Date: Wed, 1 Jun 2022 08:05:38 -0600
On Wed, 1 Jun 2022 at 03:29, Sébastien Bini <sebastien.bini_at_[hidden]>
wrote:
> If the sole point for having std::relocate is for std::swap, why not have
> a magic compiler generated constexpr std::swap that uses relocation? I fear
> that having operator reloc and std::relocate will be confusing.
>
I've found use for std::relocate elsewhere, e.g. when implementing
std::optional, std::variant. Also when decomposing std::tuple for
std::apply, std::make_from_tuple.
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 don't see the need for push_back to take a tag parameter at all; we can
> just add push_back by value: void push_back(T value);. Possibly the
> overload resolution rules will need updating, or the existing overloads
> (T&& / T const&) could be removed.
>
> How would you update those rules?
>
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]
or, if not that,
> While the copy-and-swap (in this case, reloc-and-swap) idiom works fine
> for this assignment operator, for gsl::not_null a memberwise implementation
> would also work, indicating that this assignment operator should be a
> defaultable special member function:
> >
> > not_null& operator=(not_null) = default;
>
> That should work. Although I believe that assignment relocation could
> happen in terms of destruction + relocation, at least for types that
> support operator reloc().
>
The user can write destroy+relocate if they want; the language shouldn't be
doing that.
> In turn, this indicates that the spelling for the relocation operator
> should simply be T(T) - that is, it should have the syntax of a
> constructor. Since a by-value constructor is currently invalid, this is
> free syntax.
>
> I am not thrilled by this :/
>
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).
> - T(T) does not convey the intent like "operator reloc(T&&)" does;
> - Users may find it confusing to have a constructor overload that
> takes parameters by value;
>
> And an operator that behaves like a constructor / static constructor isn't
confusing?
>
> - T(T) would mean that the destructor of the parameter is called when
> the function exits, which is not the case;
>
> The specialness of the constructor (relocating memberwise) is what
prevents the destructor being called.
>
> - That syntax would only imply that a copy of the object is performed
> and that copy is placed in the constructor parameter. While this is not
> what happens: `reloc obj` will simply pass 'obj' as parameter, and we lie
> when we say it's a pr-value. Unlike assignment-relocation (T&
> operator=(T)), where the parameter is an actual pr-value, returned by
> reloc. Unless I am missing something?
>
> For assignment, it'd be better if also there is no new prvalue created, so
that should be elided/avoided. It's not a lie to say it's a prvalue if
that's the magic of reloc keyword-operator.
>
> - This syntax would allow code like: T a; T b{a}; What happens then?
> Is the copy constructor called or the relocation constructor? If the
> latter, then `a` will be double-freed. If the former, then how does
> overload resolution figure this one out?
>
> The copy constructor; a is an lvalue, so the constructor taking lvalue is
preferred. If T doesn't have a copy constructor, this is ill-formed.
wrote:
> If the sole point for having std::relocate is for std::swap, why not have
> a magic compiler generated constexpr std::swap that uses relocation? I fear
> that having operator reloc and std::relocate will be confusing.
>
I've found use for std::relocate elsewhere, e.g. when implementing
std::optional, std::variant. Also when decomposing std::tuple for
std::apply, std::make_from_tuple.
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 don't see the need for push_back to take a tag parameter at all; we can
> just add push_back by value: void push_back(T value);. Possibly the
> overload resolution rules will need updating, or the existing overloads
> (T&& / T const&) could be removed.
>
> How would you update those rules?
>
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]
or, if not that,
> While the copy-and-swap (in this case, reloc-and-swap) idiom works fine
> for this assignment operator, for gsl::not_null a memberwise implementation
> would also work, indicating that this assignment operator should be a
> defaultable special member function:
> >
> > not_null& operator=(not_null) = default;
>
> That should work. Although I believe that assignment relocation could
> happen in terms of destruction + relocation, at least for types that
> support operator reloc().
>
The user can write destroy+relocate if they want; the language shouldn't be
doing that.
> In turn, this indicates that the spelling for the relocation operator
> should simply be T(T) - that is, it should have the syntax of a
> constructor. Since a by-value constructor is currently invalid, this is
> free syntax.
>
> I am not thrilled by this :/
>
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).
> - T(T) does not convey the intent like "operator reloc(T&&)" does;
> - Users may find it confusing to have a constructor overload that
> takes parameters by value;
>
> And an operator that behaves like a constructor / static constructor isn't
confusing?
>
> - T(T) would mean that the destructor of the parameter is called when
> the function exits, which is not the case;
>
> The specialness of the constructor (relocating memberwise) is what
prevents the destructor being called.
>
> - That syntax would only imply that a copy of the object is performed
> and that copy is placed in the constructor parameter. While this is not
> what happens: `reloc obj` will simply pass 'obj' as parameter, and we lie
> when we say it's a pr-value. Unlike assignment-relocation (T&
> operator=(T)), where the parameter is an actual pr-value, returned by
> reloc. Unless I am missing something?
>
> For assignment, it'd be better if also there is no new prvalue created, so
that should be elided/avoided. It's not a lie to say it's a prvalue if
that's the magic of reloc keyword-operator.
>
> - This syntax would allow code like: T a; T b{a}; What happens then?
> Is the copy constructor called or the relocation constructor? If the
> latter, then `a` will be double-freed. If the former, then how does
> overload resolution figure this one out?
>
> The copy constructor; a is an lvalue, so the constructor taking lvalue is
preferred. If T doesn't have a copy constructor, this is ill-formed.
Received on 2022-06-01 14:05:52