Another point: std::relocate_at(p, q) is just new (p) T(std::relocate(q)). So if anything it's std::relocate_at that's unnecessary.
I agree with you on the confusion; since we expect non-experts to use operator reloc only, std::relocate could be given a longer, more technically oriented name.
I agree with both statements.
As a tiebreaker, prefer passing by value for prvalue arguments. Specifically, I would amend [over.ics.rank]/3.2.3 to read:
- neither of S1 and S2 bind a reference to an implicit object parameter of a non-static member function declared without a ref-qualifier, and either:
- S1 binds an lvalue reference to an lvalue, and S2 does not, or:
- S1 binds an rvalue reference to an xvalue, and S2 does not, or:
- S1 does not bind a reference, and S2 binds a reference to a prvalue, or:
- S1 binds an rvalue reference to a prvalue, and S2 binds an lvalue reference [Example:
int i;
int f1();
int&& f2();
...
int g2(const int&);
int g2(int);
int g2(int&&);
int j2 = g2(i); // calls g2(const int&)
int k2 = g2(f1()); // calls g2(int)
int l2 = g2(f2()); // calls g2(int&&)
...
- end example]
That sounds nice and shouldn't break existing APIs as they would today be considered as ambiguous.
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.
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?
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?
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?
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?
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?
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 :/