Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++11 `T&&` parameter losing its `&&` correct terminology?

Tags:

c++

c++11

The following C++11 code does not compile:

struct T {};

void f(T&&) { }

void g(T&& t) { f(t); }

int main()
{
    g(T());
}

The correct way to do this is:

void g(T&& t) { f(move(t)); }

This is very difficult to explain in the correct natural language terminology. The parameter t seems to lose its "&&" status which it needs to have reinstated with the std::move.

What do you call the T() in g(T()) ?

What do you call the T&& in g(T&& t) ?

What do you call the t in g(T&& t) ?

What do you call the t in f(t) and f(move(t)) ?

What do you call the return value of move(t)?

What do you call the overall effect?

Which section(s) of the standard deal with this issue?

like image 278
Andrew Tomazos Avatar asked Jan 06 '13 18:01

Andrew Tomazos


3 Answers

The key point is that a parameter T&& b can only bind to an rvalue, but when referred to later the expression b is an lvalue.

So the argument to the function must be an rvalue, but inside the function body the parameter is an lvalue because by then you've bound a reference to it and given it a name and it's no longer an unnamed temporary.

An expression has a type (e.g. int, string etc.) and it has a value category (e.g. lvalue or rvalue) and these two things are distinct.

A named variable which is declared as T&& b has type "rvalue reference to T" and can only be bound to an rvalue, but when you later use that reference the expression b has value category "lvalue", because it has a name and refers to some object (whatever the reference is bound to, even though that was an rvalue.) This means to pass b to another function which takes an rvalue you can't just say f(b) because b is an lvalue, so you must convert it (back) to an rvalue, via std::move(b).

like image 99
Jonathan Wakely Avatar answered Sep 29 '22 16:09

Jonathan Wakely


All parameters are lvalues, even if their type is "rvalue reference". They have names, so you can refer to them as often as you want. If named rvalue references were rvalues, you would get surprising behavior. We do not want implicit moves from lvalues, that's why you have to explicitly write std::move.

like image 30
fredoverflow Avatar answered Sep 29 '22 18:09

fredoverflow


What do you call the T() in g(T()) ?

A temporary (which is movable).

What do you call the T&& in g(T&& t) ?

T&& is an r-value reference and represent an object that can be moved.

What do you call the t in g(T&& t) ?

t is actually an l-value since you can refer to it by name.

What do you call the t in f(t) and f(move(t)) ?

  1. l-value
  2. l-value being converted into an r-value reference when returned by move()

What do you call the return value of move(t)?

An r-value reference

As a note; you should maybe call the struct C, and write a separate example where T is actually templetized. The code needs to be different then, because in a function template< typename T > void f( T&& t ); you cannot simply use std::move() without being very careful, since T can actually be a const&, in which case you must not use std::move() but instead use perfect forwarding with std::forward< T >( t )

like image 35
gustaf r Avatar answered Sep 29 '22 16:09

gustaf r