Bengt/Arthur : 

> Neither in the formal proposal or in the OP it has not been made clear what the forward operator would mean when applied to a by value variable.

"Forward-op'ing" a value variable should move : I think the semantics of forwarding are "pass this along transparently", and as such the true definition of forwarding is really "static_cast<decltype(x)&&>(x)", despite "decltype(x)(x)" sometimes being mentioned as a synonymous of forwarding, as they are equivalent with forwarding references. I agree with Bengt : moving an lvalue from a non-owning scope is more often than not wrong and shouldn't be encouraged by the language. 

Jason :

>I recall that for a time, P0849 which makes `auto(expr)` create a
prvalue of the appropriate type also included `decltype(auto)(expr)`

It's interesting that you mention this paper, it popped in my head shortly after sending the mail, and I thought I should have proposed auto&&(x) instead to be more coherent with this proposal (and the rest of the language esp. forwarding references variable declaration). I'd still long for something even shorter though. 

Le sam. 16 janv. 2021 à 13:02, Bengt Gustafsson via Std-Proposals <> a écrit :

@Barry: Was the reason for shooting the proposal down that the >> was viewed as contentious with template close brackets or were there non-syntax related issues? It does seem that at least => or -> is free of such risks as = or - can't end a template declaration. Actually I can't see any real parsing issue with >> either but that's another matter.


Yoe are of course right in that conceptually move and forward are different operations, but as  Barry noted in his proposal ( forward does the same thing as move except for lvalue references. I think that if we have an easy to type forward operator such as -> it will be used also for the move operation. And this is a good thing, as it avoids the pitfall of accidentally moving out of universal references.

The long version:

What I was trying to say was that moving a lvalue reference is not a very common situation, most moves we do today move by value variables or rvalue reference parameters. When we write move on a universal reference we are often not aware that our caller's by value variables will be moved from without their consent, which leads me to believe that the behavior of forward is the safe choice even when we write move. Example:

struct MyType {
    // Standard setter pair
    void setName(string&& n) { m_name = std::move(n); }
    void setName(const string& n) { m_name = n; }

    string m_name;

// Forwarding function
template<typename T> void set(MyType& o, T&& n) { o.setName(move(n)); }  // #1

// Use
int main()
    MyType o;
    string name = "Tarzan";

    set(o, name);

  cout << "My name is " << name << ".";      // #2


At #1 we move the n parameter as we want to use the best setter. However, this should have been forward. Maybe we made this function a template at a later stage, not realizing that we thereby made our reference universal and move incorrect.

At #2 we see the consequences, the name variable is now in the move from state, which typically outputs as the empty string.

So in this case move should definitely have been forward. I must say that I'm hard pressed to find any valid use case for moving out of a lvalue reference that does not arise due to a wrongly declared parameter- or return type:

// Non-template version of above. The type should clearly have been string&& to indicate to caller that it will move n out.
void f(MyType& o, string& n) { o,setName(move(n)); }

// If you really want to give away your name member you should not return it by lvalue reference, choices are between rvalue reference or by value.
string& releaseName() { return m_name; }

// If you have a lvalue reference you got as a return value it is implied that you are allowed to change it, and moving out is a type of change. But when
// the intention is to offer the reference for moving by value or by rvalue reference is a way to indicate this and by always using forward surprises can be avoided.
string& nn = releaseName();

// The one valid use case of moving from a lvalue reference I could find is when you have extracted a lvalue reference to a subobject and later decide you can
// move from it.
ComplexObject obj;
string& userName = obj[ix].nameMap["user"];   // #3
if (userName can be safely moved out)

My conclusion is that as long as you have a (non-const) lvalue reference you got from somewhere else that don't know you're going to move from it
there is either the wrong return type/parameter type or a reliance of moved from value being the same as the empty value. The use case at #3 is valid but rare. If we didn't already have move and were contemplating addding a forwarding operator I would say that in this case static_cast<string&&>(userName) would be the way to go, using the forwarding operator for all cases when it behaves as move.

Maybe there is some prominent use case of move that I didn't see?

If not it seems that with a forwarding operator we should teach to use it for both forward and move situations, avoiding nasty surprises arising from selecting move instead of forward for brevity. Recommending using static_cast for the specialist case when you find the optimization of moving from a lvalue reference into a data structure you know enough about to be certain that this is correct. With this in mind it seems logical to look for another name than "forwarding operator".

An alternative would be to make the forwarding operator a forwarding only operator by making it an error to apply it to a by value variable, forcing the use of move in this case. However this begs the question whether we also would add a move operator, which works for by value variables and rvalue refeerences but not for lvalue references. Personally I think that teaching would be easier with just one operator.

Background investigations:

A little test program (Godbolt: shows that in most cases move and forward actually do the same thing. Soem coments:

The direct g calls show that moving or forwarding a by value variable has the same result. (Also including the cast variants of forwarding). This means that if the spelling of forwarding is shortest, it will often be used even here, even though move is the operation.

The perfect forwarding in the f1 function works as intended, but the moving as in g1 is often a bug as it will allow first m1 call to move out of my x variable without indication at the m1 call site.

The same goes for the m3 function which takes the variable in the same way and is not often correct: If a function like m3 moves the value out its parameter type should be like m2.

Neither in the formal proposal or in the OP it has not been made clear what the forward operator would mean when applied to a by value variable. As seen by the second and third call to f1 (marked #2 and #3)  there are two possibilities, and if the forwarding operator just uses the variable type or with as a lvalue reference. However, the choice is clear: It should be forwarded as the by value type. This is obvious by the fact that if a lvalue reference would result the forwarding operator would have no effect and be useless for local by value variables. Note that this means that the forwarding operator (#2) now works exactly as move (#4) when applied to by value variables.


Std-Proposals mailing list