简体   繁体   中英

Does an rvalue keep its “status” when a const reference parameter binds to it?

Let T be an arbitrary type. Consider a function that takes a const [lvalue] reference:

void f(const T &obj);

Suppose that this function internally makes a call to another function, which has an rvalue reference overload:

void g(T &&obj);

If we pass an rvalue to f , will the rvalue reference overload of g be called, or will it fail to do so since it has been "converted"/bound to a const lvalue reference?

Similarly, if f called a function that takes an instance of T by value,

void h(T obj);

and T has a move constructor, (ie T(T &&); ), will the move constructor be called, or will the copy constructor be called?

In conclusion, if we wanted to ensure that, when calling f on an rvalue, the rvalue reference is passed around keeping its rvalue "status", would we necessarily have to provide an rvalue reference overload for f ?

Value categories are applied to expressions , not objects. obj in f is an lvalue expression, and will therefore be treated as such. Note that obj in g is also an lvalue expression; if the expression is a name for an object, then it's an lvalue.

The ability you're talking about is exactly why forwarding references exist: so that the copy/move aspects of an argument expression's value category can be preserved through function calls. f would have to become a template of the form template<typename T> void f(T&& t); , and you would have to use std::forward when passing it to g .

When you use the name of a reference as an expression, that expression is always an lvalue. No matter if it's a lvalue reference or an rvalue reference. It also doesn't matter if the reference was initialized with an lvalue or an rvalue.

int &&x = 1;
f(x); // Here `x` is lvalue.

So in void f(const T &obj) {...} , obj is always an lvalue, regardless of what you pass as an argument.

Also note that value category is determined at compile time. Since f is not a template, value category of every expression in it can't depend on arguments you pass.

Thus:

If we pass an rvalue to f , will the rvalue reference overload of g be called

No.

if f called a function that takes an instance of T by value, void h(T obj); and T has a move constructor, (ie T(T &&); ), will the move constructor be called

No.

In conclusion, if we wanted to ensure that, when calling f on an rvalue, the rvalue reference is passed around keeping its rvalue "status", would we necessarily have to provide an rvalue reference overload for f ?

Providing an overload is one option. Note that in this case you have to explicitly call std::move in the rvalue overload.

Another option is using forwarding references , as Nicol Bolas suggests:

template <typename T> void f(T &&t)
{
    g(std::forward<T>(t));
}

Here, std::forward essentially acts as a 'conditional move '. It moves t if an rvalue was passed to it, and does nothing otherwise.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM