On Wed, 1 Jun 2022 at 03:29, Sébastien Bini <sebastien.bini@gmail.com> 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.