Hi all,

I have another idea, which is to use recursion in the manner of operator->().  For example, let's say that structured binding for tuple-like class types first looks for `get<std::make_index_sequence<std::tuple_size<E>::value>>(reloc e)` (the exact syntax isn't important, but it should be something clearly novel and `e` should be prvalue if the original object was).  Then, whatever this call returns (which must have the same `tuple_size` as `E`) is in turn submitted for structured binding (as a prvalue). 

Of course, library authors will then need to create a struct type with the appropriate number of fields, but it won't be difficult for compiler authors to write an intrinsic to do that, and once it's available for std::tuple and std::array then everyone else can just recurse to those types.  Anyone who can't/ doesn't want to recurse to the Library can use preprocessor hackery, other code generation, or possibly metaclasses (once those are available), to support any sensible number of elements.

It'd be ugly to convert `std::array<T, N>` to essentially `struct { T t0, t1, ...tn-1; };` but N should be small (at least until we get variadic structured binding) and compiler authors can always hack in a better solution for privileged Library classes as long as the general case can be made to work for non-Standard library authors.  A more ambitious solution would be to allow returning arrays from functions.
I really like this idea. It's a bit of a shame though that std::pair will have to provide its own get<index_sequence<0, 1>> and perform an extra relocation, while it could already fit the data-member binding protocol :/ But that's a minor detail.
Nice solution though :)

For example, one could write:

template<std::size_t... I, class... T> requires std::same_as<std::index_sequence<I...>, std::index_sequence_for<T...>>
auto get<std::index_sequence<I...>>(my_tuple<T...> t) {
    union { my_tuple<T...> tt; } = {.tt = reloc t}; // prevent destructor
    return std::tuple<T...>(std::relocate(&get<I>(tt))...);

The union trick is a bit hacky, and it's not exception-safe (if exceptions leak through the std::relocate calls). Also what happens if I want to write this function for a custom class, but only want to provide a subset of all data-members? Then, if I use the union trick, I need to manually call the destructor on all non-relocated data-members...
But well, this is just reserved for library writers so it may be acceptable.

I see another unorthodox solution though. This get<index_sequence> is to be called by the language, and not manually. Hence what if the signature were:
auto get_bindings<E>(D1 d1, D2 d2, D3 d3, ...), where Di is the i-th subobject of E in declaration order, passed as prvalue. There are as many parameters passed as there are subobjects in E. The E template parameter must not be there if defined as an E's member function (which mmakes the function non-template). If such a function exists then the language will manually split E into all of its subobjects by relocation, and make the function call. We can keep the recursive bit so as long as there exists such a get_bindings function that matches for the return type, it is applied.

This way we can safely write our own without being hacky:
template<class... T>
auto get_bindings<my_tuple<T...>>(T... t) {
    return std::tuple<T...>(reloc t...);

If we need to retrieve base class data-members, or nested data-members, that's still doable:
struct B { T _b }; struct D : B { T _d; T_arr[2]; };

template <>
auto get_bindings<D>(B base, T d, T arr[2])
auto [b] = reloc base; // calls get_bindings<B>(T) if it exists, or else relies on data-member protocol.
auto [a1, a2] = reloc arr;
return std::tuple{reloc b, reloc d, reloc a2};

When structured relocation is used we can issue a warning if a get_bindings is declared for the class type but its parameters don't match (it may very well lead to an error if other binding protocols fail to apply). That way we can warn the user that they forgot to update the get_binding function when they changed their class layout.

I know it's unorthodox, but it's safe. I wonder how it mixes with virtual inheritance; maybe we should forbid such types...

About tuples:
Another problem is that there are no std::tuple constructors that allow to relocate its input parameters. Sure, we could write one, but I fail to see a good signature. I fear, because of the variadic template, that either all input parameters are prvalues, or all are forwarding references. With such a signature we cannot build a tuple by moving its first parameter and relocating its second.

Acknowledging that, we could simply write (Types... being the std::tuple template parameters):
std::tuple::tuple(Types... types)

However there are already:
std::tuple::tuple(Types const&... types)
template <class... UTypes>
std::tuple::tuple(UTypes&&... types)

I can already see people asking why the wrong constructor was picked. See for instance: std::tuple<std::string, RelocOnlyTp, int>{reloc a, reloc b, reloc c}, but unfortunately c is long long int in our Murphy scenario, which makes the forwarding reference constructor being picked because of the type discrepancy. Then the whole thing is ill-formed because `b` is relocate-only and cannot be moved.

I can think of another solution:
template <class... UTypes>
std::tuple::tuple(UTypes... types)

Using this constructor, all parameters must be passed as prvalues. This means for users to use reloc on a parameter they wish to relocate, std::cref on a parameter they wish to copy, and a new std::mref on a parameter they wish to move (std::mref being a helper function that builds a new std::move_reference_wrapper. We cannot use std::move because it returns an xvalue, not a prvalue...). Thankfully for us, std::cref and std::mref return a prvalue. This tuple constructor would be able to detect the two kinds of reference wrappers and unwrap them to call the appropriate constructor.

However I see two potential problems. First, I am not sure how things might conflict against the tuple(UTypes&&... types) constructor. Second users have been taught to use std::move since C++11, and now they would need to use std::mref in some circumstances...