On Mon, 19 Dec 2022 at 18:55, Arthur O'Dwyer <arthur.j.odwyer@gmail.com> wrote:
On Mon, Dec 19, 2022 at 12:05 PM Edward Catmur via Std-Proposals <std-proposals@lists.isocpp.org> wrote:
On Mon, 19 Dec 2022 at 17:52, Giuseppe D'Angelo via Std-Proposals <std-proposals@lists.isocpp.org> wrote:

I'm not really sure without reading something concrete, but this seems
to be falling the same bucket of synthesizing copies / moves /
relocations (P1144) (/ [[trivial_abi]]), i.e. things compose
"automatically" by default or you've got type traits to allow you to
choose a composition strategy.

Yes, I agree on that part, if you mean that they should compose automatically for defaulted special member functions (including for aggregates).

The most recent syntax
optional(optional) [[no_parameter_reloc(is_no_parameter_reloc<T>)]];
variant(variant) [[no_parameter_reloc(is_no_parameter_reloc<Types> or ...)]];
certainly seems to be gravitating toward the P1144 propagation solution, as seen in my libc++ fork here for example:



The difference I see between this and P1144 is that this is conflating trivial_abi with trivial relocatability (even though there are types which are trivially relocatable that we probably don't want to be trivial_abi, such as `std::array<int, 1000>`

I'm not sure I understand your position here. std::array<int, 1000> *is* trivial, so it certainly has trivial ABI. And that's fine; ABI says that a class type that is too large to be passed in (some small number of) registers is passed by copying from some stack location to another. Trivial ABI just says to, constructors and destructors notwithstanding, pass the object in the manner that ABI prescribes for a trivial class type of the same layout.

) and even with non-trivial relocatability.

I see it as allowing to express the fine-grained distinction between several possibilities, some of which already exist of course:

* trivial
* trivial ABI
* trivially relocatable but not trivial for the purposes of ABI
* non-trivially relocatable
* relocatable via copy/move+destroy
* immovable

It's almost like a "customizable trivial_abi" at this point — "take this type and pass it by trivial_abi, except that each time the object teleports from one memory address through a register to another memory address, run this code."
And the problem I see with that is that the "teleport" operation is really two operations: "slurp from memory into a register" on the caller's side, and "blat from register back into memory" on the callee's side. For types that are trivially relocatable, both operations are trivial.  For types that aren't trivially relocatable, it's unclear what these two operations should actually do, and in particular whether the in-register representation can be used directly at all. 

Ah; no, that isn't how a nontrivial relocating constructor works. It doesn't place the object representation into registers like a trivial relocation (of a small enough class) would; it will in all likelihood have the same ABI as a copy or move constructor, i.e. taking two pointers, to the source and destination objects. (Although it has the additional effect of ending the lifetime of the source object.)

And collapsing them both into `T(T)` (however it's ultimately spelled) feels like a problem.

Well, the former is spelled `T(T) = default`. Those extra 2 keywords do a lot of work, here and elsewhere,

Have you tried "manually implementing" this idea, by showing some simple examples of `T(T)` and then showing the machine code generated from them?
Assume that nothing is inlined or optimized — so you're not allowed to "cheat" by eliding any operations — just show exactly how the `T(T)` constructor would be codegenned, and then what a call to it would look like, and so on.

It would perhaps be more illuminating to show the equivalent C++ code. Given a type A that is relocatable through move+destroy, and a type T that contains an instance of A:

struct A { A(A&&); ~A(); };
struct T { A a; int i; };

The automatically-generated (implicitly defaulted) T::T(T) would, modulo assumptions of ABI, be equivalent to the following (omitting exception handling, which Sébastien is certainly mindful of):

void T_T_T(T* dest, T* src) {
    std::construct_at(&dest->a, std::move(src->a));
    src->a.~A();
    dest->i = src->i;
}

So there's really no need to compile down to machine code; the code executed by the relocating constructor is fully expressible in today's C++ - though I should point out that I'm very much not advocating specifying it via source translation.

HTH,
Arthur