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:
long double
is stored, since it is not contained in object test<long double>
?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)
}
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 andtest<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:
T
is passed by value (e.g. int
) -> T&&
T
is a lvalue reference (e.g. int&
) -> T&
T
is another type of reference (e.g. int&&
) -> T&&