简体   繁体   中英

Why rvalue reference as return type can't be initialization of non-const reference?

I read this question and I know that an rvalue referenec is an lvalue.

However, for this code, example 1,

int &&fun() {
    return 1;
}

int main() {
    int &a = fun();
}

When I compile it:

 error: invalid initialization of non-const reference of type 'int&' from an rvalue of type 'int'

So the C++ compiler tells me the return type of fun is an rvalue.

How does a rvalue reference become an rvalue?

I think the compiler should treat lvalue reference and rvalue reference by the same way, but this code, example 2,

int & fun(){
    int b;
    return b;
}
int main(){
    int & a=fun();
}

can be compiled (nevertheless, I get a warning).

I think maybe the return type of fun has changed at some point.

Trying to compile example 3:

int &&fun() {
    return 1;
}

int main() {
    decltype(fun()) b = 1;
}

it compiles successfully. So I can say the return type of fun is really an rvalue reference.

So, why does an rvalue reference become an rvalue?

Here is example 4:

int &&a = 1;
int &b = a;

It compiles and tells us an rvalue reference can be bound to an lvalue reference.

Now, what about those two questions:

  1. In example 1, is fun() an rvalue?
  2. In example 1, is fun() an rvalue reference?

Example 3 tells us fun() is an rvalue reference,and example 4 tells us an rvalue reference can be bound to an lvalue reference (both const and non-const). Then why can't the fun() from example 1 be bound to an lvalue reference?

Example 4 also indicates that an rvalue reference is an lvalue, but the compilation error in example 1 tells us that fun() there, which is proved to be an rvalue reference in example 3, is an rvalue. So, is an rvalue reference lvalue or rvalue?

If the cause is that fun() is just an expression, which exists temporarily and will die right away, why is fun() from example 2 not be regarded an rvalue while it is also just an expression without a name? What difference is there between a function expression of a function returning an lvalue reference and an rvalue reference?

I know that an rvalue reference is an lvalue.

You're talking about two different things: type and value category . eg

int&& ri = 0; // ri's type is rvalue reference (to int)
              // ri's value category is lvalue; it's a named variable.

Given your 1st sample, what fun() returns is an xvalue, which belongs to rvalues.

The following expressions are xvalue expressions:

  • a function call or an overloaded operator expression, whose return type is rvalue reference to object, such as std::move(x) ;

then,

int &a = fun(); // fails; lvalue-reference can't bind to rvalue

In the 2nd sample, what fun() returns is an lvalue,

The following expressions are lvalue expressions:

  • a function call or an overloaded operator expression, whose return type is lvalue reference, such as std::getline(std::cin, str) , std::cout << 1 , str1 = str2 , or ++it ;

then

int & a=fun(); // fine; lvalue-reference could bind to lvalue

In the 3rd sample,

decltype(fun()) b = 1; // the return type of fun() is rvalue-reference;
                       // this has nothing to do with the value category of its return value
                       // b's type is rvalue-reference too, btw its value category is lvalue

In the 4th sample,

int &&a = 1; // fine; rvalue-reference could bind to rvalue
             // a's type is rvalue-reference, its value category is lvalue
int &b = a;  // fine; lvalue-reference could bind to lvalue
             // b's type is lvalue-reference, its value category is lvalue

Non-const references cannot bind to rvalues, it's as simple as that.

int & a=fun();

does not work because a is a non-const reference and fun() is an rvalue expression.
In the second case, fun() returns a non-const lvalue reference, which can bind to another non-const reference, of course.

decltype(fun()) b=1;

works because decltype(fun()) is int && and can thus bind to the integer literal 1 .


In example 1, is fun() an rvalue?

Yes.

In example 2, is fun() an rvalue reference?

No, it's an lvalue reference.

Example 3 tells us that fun() is an rvalue reference and example 4 tells us an rvalue reference can be bound to an lvalue reference (both const and non-const). Then why can't fun() from example 1 be bound to an lvalue reference?

Because the function fun returns an rvalue reference, but fun() itself is an rvalue expression . fun() is an rvalue.

Example 4 also indicates that an rvalue reference is an lvalue, but the compilation error in example 1 tells us that fun() there, which is proved to be an rvalue reference in example 3, is an rvalue. So, is an rvalue reference lvalue or rvalue?

An rvalue reference is an lvalue.

If the cause is that fun() is just an expression, which exists temporarily and will die right away, why is the fun() in example 2 not regarded an rvalue while it is also just an expression without a name? What difference is there between a function expression of a function returning an lvalue reference and an rvalue reference?

Because in example 2, fun() is an lvalue. From N4296, §3.10/1.1:

[...] the result of calling a function whose return type is an lvalue reference is an lvalue.


Regarding the warning you get for example 2, you should show the exact message. It's probably just because you return a reference to a local variable, though. Local variables have limited lifetime, so referencing them beyond their lifetime is undefined behavior, hence the warning.

First of all, this code exhibits undefined behavior:

int && fun(){
    return 1;
}

Here you're returning a dangling reference to 1 , which goes out of scope.

How does a rvalue reference become an rvalue?

In order to understand this it's good to view references not as another syntax for pointers, but as another name for some already existing object.

Then it's good to go over reference initialization rules:

The first reference initialization rule states that a reference may be initialized ("bound") to a reference-compatible value. That means

  • int& can bind to int&
  • int&& can bind to int&&
  • const int& can bind to int&

In this case the actual referred-to value of the right-hand side is not retrieved, but is directly bound to the new reference. Note that int& is not compatible with int&& , these are distinct types.

The second reference initialization rule states that a const lvalue reference ( const int& ) and an rvalue reference ( int&& ) may bind to:

  • xvalue or prvalue
  • as the last resort to anything else

In case of the latter the reference binds to the result of the expression. In the case of const int& x = fun() , the result of calling fun() will first be "materialized" (retrieved), and then its value will be bound to the reference.

But for that to happen, the lvalue reference must be const . That's why the error states that a non- const int& cannot bind to int , because int is the result of evaluating fun() .

The key is that the value category of an expression does not only depend on its type, eg

int&& a = 1;
int&& fun();
// int&& ri = a; // ill-formed, the expression a is of type int&&, but is an lvalue
int&& ri = fun(); // ok, the expression fun() is of type int&&, and is also an rvalue

In addition, as rustyx pointed out in his answer , the function definition

int && fun(){
    return 1;
} 

would probably result in undefined behavior, because the temporary object will be destroyed immediately after the execution of the return statement.

I think you are mixing rvalue and rvalue reference . In your first example

int && fun(){
    // 1 is an rvalue so it can be bound to an rvalue reference
    // this will produce undefined behavior though because you
    // a returning a dangling reference to an temporary that will
    // go out of scope at the end of this function
    return 1;
}
int main(){
    // you are trying to take a reference to a temporary object.
    // this is (deliberately) not valid
    int & a=fun();

    // One legal way of doing this is by declaring your reference const:
    const int& b = fun(); 
    // because this extends the lifetime of the temporary object returned
    // by fun() to match the lifetime of the reference.
}

In your second example:

int & fun(){
    // you have allocated an new int in the free store so the 
    // lifetime of this int is until the main exits. The return
    // type here is an lvalue that can be safely bound to an 
    // lvalue reference
    return *(new int);
}
int main(){
    // binding lvalue reference to lvalue this is ok
    int & a=fun();
}

In your third example

int && fun(){
    // 1 is an rvalue and can be bound to an rvalue reference
    return 1;
}
int main(){
    // decltype(fun()) is equal to int&& so it's ok to bind
    // an rvalue reference to an rvalue
    decltype(fun()) b=1;
}

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