> I agree with the need for the attribute, but wouldn't it be better to attach it
> to the relocating constructor?
>
> class T { public: T(T) [[no_parameter_reloc]]; };
>
> That way code that wants to be backwards compatible has fewer places where a
> feature-test macro check is required.
Could be placed on the constructor itself as well, but then you need to declare the constructor which you may not want to (rule of zero). See std::array.
> 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.
Not for the moment. I'm still struggling to have time to work on this. But writing an implementation is one of the next things to do on the topic.
> Yes, that's another option. Really, it's up to the implementation what they want to do about ABI:
>
> * full break, use callee-destroy for all relocatable types (for those who don't
> care about ABI)
> * break with opt-out - an attribute to mark opt-out, and propagation mechanisms
> (these could be standardized at a later date)
> * no break, but opt-in - an improved [[trivial_abi]] that actually checks that
> the type is trivially relocatable
> * no break, use callee-destroy only for relocate-only types.
> * and those that are callee-destroy already don't need to do anything!
>
> And more or less orthogonal to that is whether to use trivial ABI for
> trivially-relocatable nontrivial types passed as callee-destroy, or to always
> pass such types on the stack. An implementation could also choose something more
> complex such as passing trivially-relocatable types with trivial ABI, but
> non-relocate-only nontrivially-relocatable types as caller-destroy.
>
> Given that there are so many options (and maybe more that I've missed), maybe it
> would be best just to list them as a sign that we've considered ABI as an issue.
> If one of them gets settled on across the current caller-destroy platforms (are
> there any other than Itanium?), that can be standardized (along with propagation
> mechanisms, if appropriate) at a later date.
Yes, I agree. I'm going to mention the different ABI approaches, and only mandate that functions that take a relocate-only parameter by value are callee-destroy. If implementers need a standardized approach to make the ABI evolve to callee-destroy, that can be in a separate paper.
Note that, even with callee-destroy being enforced only for relocate-only types, it might still cause ABI breaks:
do_something(std::lock_guard<std::mutex> guard);
// [...]
void foo()
{
std::mutex m;
std::jthread t1([&] { do_something(std::lock_guard{m}); });
std::jthread t2([&] { do_something(std::lock_guard{m}); });
}
This code compiles fine on my gcc, and works fine even. Should we be tempted to make std::lock_guard relocate-only, it would imply a potential ABI break on such functions... How often is this seen in real code? Not often I hope...
We could simply resolve this issue by stating that no relocation constructor will be added to lock_guard, but that does not fix more pernicious cases, like functions with signature: void foo(non_null<unique_ptr<int>>); non_null and unique_ptr will get a relocation ctor, and then non_null<unique_ptr> will be automatically relocate-only, potentially changing the ABI of foo.
I don't know to what extent we can simply ditch those cases by saying they are bad code (really, who would pass a non_null<unique_ptr> by value today?), and the ABI break is the price to pay for writing such bad code in the first place. Maybe an ABI break on such functions is acceptable. I doubt any major libraries provide such APIs?
Regards,
Sébastien