Date: Wed, 1 Jun 2022 17:53:15 +0200
> 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 :/
> 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 :/
Received on 2022-06-01 15:53:28