The user can write destroy+relocate if they want; the language shouldn't be doing that.
As a user I would feel uncomfortable explicitly calling the destructor on *this to reconstruct it again.
Well, quite. And destroy-and-recreate is a legitimate implementation of copy- and move-assignment operators (see e.g.
rapidjson::Document) but I would always advise to use copy-and-swap idiom.
This does require a bit of a conceptual leap, yes. The key is to understand that operator reloc *changes the value category* of a named object, from lvalue to prvalue. This is because relocation is the inverse operation to prvalue materialization. So by the time the relocating constructor is invoked, the argument is already a prvalue (just, uh, a prvalue that occupies storage).
Just to be sure I understand correctly: the reloc operator would no longer return a newly built instance (i.e. a temporary), but instead would merely transform the value category from lvalue to prvalue?
And then whether a temporary is materialized or not depends on the context, right?
Yes, correct. One way to think of it is as if the preceding scope were transformed into an anonymous function returning that value.
Consider a type T with that putative relocation constructor T(T):
Case 1:
void foo(T a_foo);
T var;
foo(reloc var);
In `foo(reloc var);` is a_foo constructed using T(T)?
If so then `var`'s destruction is delegated to T(T), which prevents the destructor call of its parameter, right?
Yes, correct. Except that the call to T(T) may be elided, in which case `var` is constructed directly in the parameter `a_foo` of `foo`.
Case 2:
Now let's add a T& operator=(T):
T objA, objB;
objA = reloc objB;
Following the same logic as above, objB is passed to relocation constructor, and then to objA's assignment operator, right?
Yes, modulo elision.
Case 3:
T obj;
reloc obj; // premature end-of-scope
This turns obj into a prvalue that is captured nowhere. I guess in this case the destructor of obj is simply called?
Yes.
Case 4:
void bar(T&& bar_param);
T var;
bar(reloc var);
Again, reloc var turns var into a prvalue, but bar cannot capture it. So a temporary object of type T is created and its address is passed to bar, right?
The rvalue reference binds directly to the prvalue; there is no temporary object created. So &var == &bar_param.
If I understand correctly, then this could simplify some aspects of how operator reloc works.
However I fear many users will be lost (most C++ programmers that I know have no knowledge of value categories), and will find it quite disturbing to have a relocation constructor with that signature T(T). Sure operator reloc(T&&) may be confusing as well, but my personal take is that it would be less so :/
That's an opportunity for educators, then. Really, this simplifies the value category model: copy constructors (prefer to) construct from lvalues, move constructors from xvalues, and relocation constructors from prvalues. So that's the model to use.
There is another reason to model relocation as a by-value constructor: it enables delegating relocation to another constructor i.e. T(T src) : T(reloc src, ...) {}. That said, the delegated-to constructor would not benefit from the default memberwise relocation that the relocating constructor does as a special member function, and also it would not benefit from the destructor being obviated, so this would be extreme expert level only. But it's still worth enabling.