简体   繁体   中英

Lvalue reference constructor is called instead of rvalue reference constructor

There is this code:

#include <iostream>

class F {
public:
   F() = default;
   F(F&&) {
      std::cout << "F(F&&)" << std::endl;
   }
   F(F&) {
      std::cout << "F(F&)" << std::endl;
   }
};

class G {
   F f_;
public:
   G(F&& f) : f_(f) {
      std::cout << "G()" << std::endl;
   }
};

int main(){
   G g = F();
   return 0;
}

The output is:

F(F&)
G()

Why F(F&) constructor is called instead of F(F&&) constructor in constructor of class G ? The parameter for constructor of class G is F&& f which is rvalue reference but constructor for lvalue reference is called.

Why F(F&) constructor is called instead of F(F&&) constructor in constructor of class G?

Because f is an lvalue. Even though it is bound to an rvalue, and its type is rvalue reference to F , it is also a named variable . That makes it an lvalue. Don't forget that the value category of an object is not determined by its type , and vice versa.

When you pass an lvalue to a function, only lvalue references can be bound to it. You should change your code as follows if you want to catch rvalues only:

class G {
    F f_;
public:
    G(F&& f) : f_(std::move(f)) {
       std::cout << "G()" << std::endl;
    }
};

Alternatively, you could use std::forward<>() , which is equivalent in this case, but makes your intent of forwarding f even clearer:

class G {
    F f_;
public:
    G(F&& f) : f_(std::forward<F>(f)) {
       std::cout << "G()" << std::endl;
    }
};

Now this last definition is easy to extend so that both lvalues and rvalues of type F can be bound to the parameter f :

class G {
    F f_;
public:
    template<typename F>
    G(F&& f) : f_(std::forward<F>(f)) {
       std::cout << "G()" << std::endl;
    }
};

This allows, for instance, to construct an instance of G this way:

F f;
G g(f); // Would not be possible with a constructor accepting only rvalues

This last version has a caveat though: your constructor will basically work as a copy-constructor as well , so you might want to explicitly define all the possible copy constructors to avoid awkward situations:

class G {
    F f_;
public:
    template<typename F>
    G(F&& f) : f_(std::forward<F>(f)) {
       std::cout << "G()" << std::endl;
    }
    G(G const&) = default;
    G(G&); // Must be defaulted out-of-class because of the reference to non-const
};

G::G(G&) = default;

Since non-template functions are preferred over instantiations of function templates, the copy constructor will be selected when constructing a G object from another G object. The same applies, of course, to the move constructor. This is left as an exercise.

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