Skip to content

stackoverflow Understanding the warning: binding r-value to l-value reference

A

You are taking a reference to a temporary object. The only legal way to do this is either :

const object& (const l-value reference), or

object&& (mutable r-value reference)

This is a (deliberate) language limitation.

further discussion:

Assigning a temporary to a reference extends the lifetime of the temporary so that it matches the lifetime of the reference. Therefore, surprisingly to many beginners, this is legal:

{
  const string& s = foo();
  cout << s << endl;         // the temporary to which s refers is still alive
}
// but now it's destroyed

However, it would normally be a logic error to take a mutable reference to a temporary so this is disallowed in the language:

{
  string s& = foo();  // this is not possible
  s += "bar";         // therefore neither is this
  // the implication is that since you modified s, you probably want to
  // preserve it
}
// ... but now it's destroyed and you did nothing with it.

here's a more realistic reason why it's probably a logic error, given:

string foo();         // function returning a string
void bar(string& s);  // this function is asserting that it intends to *modify*
                      // the string you sent it

// therefore:

bar(foo());           // makes no sense. bar is modifying a string that will be discarded.
                      // therefore assumed to be a logic error

you would have to replace the above with:

  string s = foo();
  s += "bar";
  // do something here with s

Note that there is no overhead whatsoever for capturing the temporary in a named variable (l-value).

r-value references are designed to be the subject of a move-constructor or move-assignment. Therefore it makes sense that they are mutable. Their very nature implies that the object is transient(短暂的).

thus, this is legal:

string&& s = foo();    // extends lifetime as before
s += "bar";
baz(std::move(s));     // move the temporary into the baz function.

It might help you to remember that specifying && is you asserting that you know that the variable is a mutable temporary.

But the real reason it's allowed is so that this will work:

string foo();   // function that returns a string
void bar(string&& s);  // function that takes ownership of s

bar(foo());  // get a string from foo and move it into bar

// or more verbosely:

string s = foo();
bar(move(s));

prior to c++11, bar would have to have been written one of these ways:

void bar(string s);   // copy a string

// resulting in:

const string& s = foo();
bar(s);  // extra redundant copy made here

void bar(const string& s); // const l-value reference - we *may* copy it
// resulting in:

const string& s = foo();
bar(s);  // maybe an extra redundant copy made here, it's up to bar().

NOTE: 看了上面的介绍,我想到了一个新的问题:const type &&&之间,有哪些异同。

Example one

#include <iostream>
#include <algorithm>
class AClass{
    public:
    AClass(){
        std::cout<<"constructor"<<std::endl;
        member = "hello";
    }
    AClass(const AClass& other){
        std::cout<<"copy constructor"<<std::endl;
        member=other.member;
    }
    AClass(AClass&& other){
        std::cout<<"move constructor"<<std::endl;
        std::swap(member, other.member);
    }
    public:
    std::string member;
};

AClass foo() {
    AClass x;
    return x;
}

int main(){
    std::cout<<"using const reference"<<std::endl;
    const AClass& a = foo();
    std::cout << a.member << std::endl;
    std::cout<<"do not using const reference"<<std::endl;
    AClass b = foo();
    std::cout << b.member << std::endl;
}

输出如下:

using const reference
constructor
hello
do not using const reference
constructor
hello
#include <iostream>
#include <algorithm>
class AClass{
    public:
    AClass(){
        std::cout<<"constructor"<<std::endl;
        member = "hello";
    }
    AClass(const AClass& other){
        std::cout<<"copy constructor"<<std::endl;
        member=other.member;
    }
    AClass(AClass&& other){
        std::cout<<"move constructor"<<std::endl;
        std::swap(member, other.member);
    }
    public:
    std::string member;
};

AClass foo() {
    AClass x;
    return x;
}

int main(){
    std::cout<<"using const reference"<<std::endl;
    const AClass& a = foo();
    //a.member += " world"; // 会导致编译报错,因为a是const类型
    std::cout << a.member << std::endl;
    std::cout<<"do not using const reference"<<std::endl;
    AClass b = foo();
    b.member += " world";
    std::cout << b.member << std::endl;
    //AClass& c = foo(); //会导致编译报错,报错信息:用类型为‘AClass’的右值初始化类型为‘AClass&’的非常量引用无效
}

输出如下:

using const reference
constructor
hello
do not using const reference
constructor
hello world

编译器的RVO非常厉害。

Example two

#include <iostream>
#include <algorithm>
class AClass{
    public:
    AClass(){
        std::cout<<"constructor"<<std::endl;
        member = "hello";
    }
    AClass(const AClass& other){
        std::cout<<"copy constructor"<<std::endl;
        member=other.member;
    }
    AClass(AClass&& other){
        std::cout<<"move constructor"<<std::endl;

    }
    public:
    std::string member;
};

AClass foo() {
    AClass x;
    return x;
}
void bar(AClass& s){
    s.member += "world";
    std::cout<<s.member<<std::endl;
}
int main(){
    bar(foo()); 
}

上述代码会导致编译报错,错误信息如下:

test.cpp: 在函数‘int main()’中:
test.cpp:26:14: 错误:用类型为‘AClass’的右值初始化类型为‘AClass&’的非常量引用无效
     bar(foo()); 
              ^
test.cpp:21:6: 错误:在传递‘void bar(AClass&)’的第 1 个实参时
 void bar(AClass& s){

NOTE: : 上述代码有如下改法:

  • bar的签名改成这种方式:void bar(const AClass& s)
#include <iostream>
#include <algorithm>
class AClass{
    public:
    AClass(){
        std::cout<<"constructor"<<std::endl;
        member = "hello";
    }
    AClass(const AClass& other){
        std::cout<<"copy constructor"<<std::endl;
        member=other.member;
    }
    AClass(AClass&& other){
        std::cout<<"move constructor"<<std::endl;

    }
    public:
    std::string member;
};

AClass foo() {
    AClass x;
    return x;
}
void bar(AClass& s){
    s.member += "world";
    std::cout<<s.member<<std::endl;
}
int main(){
    AClass&& s = foo();    // extends lifetime as before
    s.member += "bar";
    bar(std::move(s)); 
}

编译后会报如下错误:

test.cpp: 在函数‘int main()’中:
test.cpp:32:21: 错误:用类型为‘std::remove_reference<AClass&>::type {aka AClass}’的右值初始化类型为‘AClass&’的非常量引用无效
     bar(std::move(s)); 
                     ^
test.cpp:25:6: 错误:在传递‘void bar(AClass&)’的第 1 个实参时
 void bar(AClass& s){

将上述代码改写为如下,可以成功:

#include <iostream>
#include <algorithm>
class AClass{
    public:
    AClass(){
        std::cout<<"constructor"<<std::endl;
        member = "hello";
    }
    AClass(const AClass& other){
        std::cout<<"copy constructor"<<std::endl;
        member=other.member;
    }
    AClass(AClass&& other){
        std::cout<<"move constructor"<<std::endl;

    }
    public:
    std::string member;
};

AClass foo() {
    AClass x;
    return x;
}
void bar(AClass&& s){
    s.member += "world";
    std::cout<<s.member<<std::endl;
}
int main(){
    AClass&& s = foo();    // extends lifetime as before
    s.member += "bar";
    bar(std::move(s)); 
}

去掉std::move

#include <iostream>
#include <algorithm>
class AClass{
    public:
    AClass(){
        std::cout<<"constructor"<<std::endl;
        member = "hello";
    }
    AClass(const AClass& other){
        std::cout<<"copy constructor"<<std::endl;
        member=other.member;
    }
    AClass(AClass&& other){
        std::cout<<"move constructor"<<std::endl;

    }
    public:
    std::string member;
};

AClass foo() {
    AClass x;
    return x;
}
void bar(AClass&& s){
    s.member += "world";
    std::cout<<s.member<<std::endl;
}
int main(){
    AClass&& s = foo();    // extends lifetime as before
    s.member += "bar";
    bar(s); 
}

编译后会报这样的错误:

test.cpp: 在函数‘int main()’中:
test.cpp:32:10: 错误:无法将左值‘AClass’绑定到‘AClass&&’
     bar(s); 
          ^
test.cpp:25:6: 错误:以初始化‘void bar(AClass&&)’的实参 1
 void bar(AClass&& s){

这种类型的错误,Google "cannot bind rvalue reference to lvalue"后,如下回答是非常具有参考价值的:

这种写法也是可行的:

#include <iostream>
#include <algorithm>
class AClass{
    public:
    AClass(){
        std::cout<<"constructor"<<std::endl;
        member = "hello";
    }
    AClass(const AClass& other){
        std::cout<<"copy constructor"<<std::endl;
        member=other.member;
    }
    AClass(AClass&& other){
        std::cout<<"move constructor"<<std::endl;

    }
    public:
    std::string member;
};

AClass foo() {
    AClass x;
    return x;
}
void bar(AClass&& s){
    s.member += "world";
    std::cout<<s.member<<std::endl;
}
int main(){
    bar(foo());
}

在这种写法中,foo()的返回值就是rvalue,所以函数foo的入参类型AClass&&能够bind到它。

NOTE: : 总共有两种类型的错误:

  • 将rvalue 绑定到lvalue reference
  • 将lvalue 绑定到rvalue reference