Search code examples
c++11rvalue-referencervalue

C++11 rvalue object field


Can I have class/struct with rvalue field in c++11? Like this one:

template<typename T>
struct RvalueTest{
    RvalueTest(T&& value) : value( std::forward<T>(value) ){}
    T&& value;
};

Because in the following test:

class Widget {
public:
  Widget(){std::cout << "Widget ctor  " << std::endl; }
  Widget(int h) : h(h){std::cout << "Widget ctor param " << std::endl; }

  Widget(const Widget&) { std::cout << "Widget copy ctor  " << std::endl;  }
  Widget(Widget&&) { std::cout << "Widget move ctor  " << std::endl;  }           // added this

  template<typename T>
  Widget(const T&) { std::cout << "Generalized Widget copy ctor  " << std::endl;  }

  template<typename T>
  Widget(T&&) { std::cout << "Universal Widget ctor  " << std::endl;  }

  int h;
};

RvalueTest<Widget> r(Widget(12));
std::cout << r.value.h;

I got some trash value at output (with -O2):
http://coliru.stacked-crooked.com/a/7d7bada1dacf5352

Widget ctor param
4203470

And right value with -O0:
http://coliru.stacked-crooked.com/a/f29a8469ec179046

Widget ctor param
12

WHY???

P.S. What I try to achive is a single ctor call, without any additional move/copy constructors.


UPDATED

It compiles ok with clang http://coliru.stacked-crooked.com/a/6a92105f5f85b943
GCC bug? Or it works as it should ?


Solution

  • The problem is not specific to rvalue references. You will see the same odd behaviour, if you replace T&& by const T&.

    template<typename T>
    struct ReferenceTest{
        ReferenceTest(const T& value) : value(value){}
        const T& value;
    };
    

    The problem is, that you are storing a reference to a temporary object. The temporary object is automatically destroyed at the end of the statement it was created, but you try to access it later via the reference.

    RvalueTest<Widget> r(Widget(12)); // pass reference to temporary object to constructor
    std::cout << r.value.h; // invalid reference
    

    It is possible to use rvalue references as member variables. However, you have to be careful if doing so. An example from the standard library is std::forward_as_tuple.

    auto t = std::forward_as_tuple(42);
    // t has type std::tuple<int&&>
    

    Regarding the question how to avoid the copy or move construction: Don't use rvalue reference member variables. Here are two approaches, you can use instead:

    std::unique_ptr: If copying or moving a Widget is too expensive, you can use a smart pointer:

    template <typename T>
    struct Test
    {
        std::unique_ptr<T> _value;
        explicit Test(std::unique_ptr<T> value) : _value{std::move(value)} { }
    };
    
    Test<Widget> t{std::make_unique<Widget>(12)};
    std::cout << t._value->h;
    

    The second approach creates the Widget in-place. That's what the different emplace functions of the standard containers do, e.g. std::vector::emplace_back.

    template <typename T>
    struct Test
    {
        T _value;
        template <typename ...Args>
        explicit Test(Args&&... args) : _value{std::forward<Args>(args)...} { }
    };
    
    Test<Widget> t{12};
    std::cout << t._value.h;