Search code examples
c++rvalue-reference

r-value reference as a class member


test<long double&> contains a reference so test<long double&> has the size of a pointer.

I believed that test<long double> contains long double and therefore, I expected it to have a size equal to 10 (12 aligned) bytes.

However, I was wrong. test<long double> also has the size of a pointer (g++).

So the questions are:

  • Is the code correct or is it an undefined behavior?
  • If code is correct, where this long double is stored, since it is not contained in object test<long double>?
  • When I stated that test<long double> is a container and test<long double&> is a view, is correct?
#include <iostream>

template<typename T>
struct test
{
    T &&val;
};


long double get() { return 6; }
long double &get_ref()
{
    static long double a = 5;
    return a;
}

int main()
{
    test<long double> t_by_value{get()};       // THIS IS A CONTAINER
    test<long double&> t_by_ref{get_ref()};    // THIS IS A VIEW
    std::cout << t_by_value.val << " " << t_by_ref.val << "\n";
    // output: 6 5
    std::cout << sizeof(t_by_value) << " " << sizeof(t_by_ref) << "\n";
    // output: 4 4 (on a 32 bit system)
}

Solution

  • Is the code correct or is it an undefined behavior?

    The class definition itself is correct, however use of it (or any other class which has a reference member variable) is very prone to have a dangling reference, and thus an undefined behavior if proper guard logic is not implemented. E.g. in your code, this line:

    std::cout << t_by_value.val << " " << t_by_ref.val << "\n";
    

    Refers to a dangling reference val of t_by_value, and thus is UB.

    EDIT: Thanks to apple apple's comment, there was pointed out another exception to this rule, which requires to bind the reference member data type directly when the class has aggregate initialization, [class.temporary]/6.10:

    The temporary object to which the reference is bound or the temporary object that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference if the glvalue to which the reference is bound was obtained through one of the following:

    ...

    A temporary object bound to a reference element of an aggregate of class type initialized from a parenthesized expression-list ([dcl.init]) persists until the completion of the full-expression containing the expression-list.

    Thus the code snippet provided in the question is 100% correct and doesn't have any undefined behaviour.


    where this long double is stored, since it is not contained in object test<long double>?

    In your scenario it's stored in a temporary passed to the aggregate constructor here (and which lifetime is extended to the end of the enclosing scope):

    test<long double> t_by_value{get()};       // THIS IS A CONTAINER
    

    When I stated that test<long double> is a container and test<long double&> is a view, is correct?

    Not in this case. In order to understand it better, you need to take into account how templates deduce their types. When you have a template variable declared like this:

    T&& val;
    

    You have so-called universal reference, which can be only one of two types: an rvalue reference or an lvalue reference. For any lvalue the type turns into T& for any xvalue or prvalue it turns into T&&.

    EDIT: in this case it's not a universal reference, so the references collapsed a little bit differently:

    • if T is passed by value (e.g. int) -> T&&
    • if T is a lvalue reference (e.g. int&) -> T&
    • if T is another type of reference (e.g. int&&) -> T&&