2

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)
}
Chameleon
  • 1,804
  • 2
  • 15
  • 21
  • 5
    Why should `test` contain `long double`? If you substitute `T` for `long double`, you will end up with `long double &&val;`. – Daniel Langr Feb 20 '23 at 12:25
  • "test contains long double" how do you come to that conclusion? – 463035818_is_not_an_ai Feb 20 '23 at 12:26
  • 2
    `T &&val;` is not an rvalue reference it is a [Forwarding references](https://en.cppreference.com/w/cpp/language/reference#Forwarding_references) as `test` is a template and [Reference Collapsing](https://en.cppreference.com/w/cpp/language/reference#Reference_collapsing) rules apply. – Richard Critten Feb 20 '23 at 12:27
  • @DanielLangr Because I believed that && eliminated. So &&& become & and && become value. – Chameleon Feb 20 '23 at 12:28
  • @RichardCritten this means that `long double &&val` becomes `long double val` as I believed. But then object comes with 4 bytes size. – Chameleon Feb 20 '23 at 12:30
  • 1
    @Chameleon I don't understand. When `T` is `long double`, then `T &&val;` clearly becomes `long double &&val;`. Why should that `&&` be "eliminated"? – Daniel Langr Feb 20 '23 at 12:30
  • Whether you're correct about containers and views depends on how you define "container" and "view". – molbdnilo Feb 20 '23 at 12:30
  • @DanielLangr `long double &&val` is different than `long double val` in the final `test` object? Then, what really means a `long double &&val` if you have an object of `test` and you access `val`? Is a reference? To what? Or it is a value because of reference collapsing? – Chameleon Feb 20 '23 at 12:33
  • 1
    Once `T` is instantiated, `T &&val` will be one of `long double &val` or `long double &&val`, i.e. lvalue or rvalue reference. Both are references, and their size will likely be the one for one pointer. Note that both references can become dangling and cause UB if they are used while the object they refer to no longer exist. Indeed, `test t_by_value{get()};` immediately creates a dangling reference. Using that reference later on will cause UB. – chi Feb 20 '23 at 12:33
  • @chi I understand. It is an undefined behavior then. Thank you. – Chameleon Feb 20 '23 at 12:35
  • 2
    Yes, `long double &&val` is a (rvalue) reference. Any C++ book for beginners would tell you that. – Daniel Langr Feb 20 '23 at 12:35
  • 3
    I think you're being confused by how "references to references" collapse to simple references. (i.e. `(double&&)&`, `(double&)&&` and `(double&)&` all become `double&`). A reference never collapses on its own. – molbdnilo Feb 20 '23 at 12:36
  • 2
    Just a quick refresh on reference collapsing rules: https://stackoverflow.com/questions/13725747/concise-explanation-of-reference-collapsing-rules-requested-1-a-a-2 (putting a link here since I had to look them up anyway). Important takeaway: They only ever collapse to `&` or `&&` - never to just "value". – Frodyne Feb 20 '23 at 12:37
  • 2
    Note that reference members are relatively problematic (e.g., they prevent automatic generation of copy assignment operators). Moreover, rvalue reference members are useful only rarely (as discussed, e.g., here: https://stackoverflow.com/q/4774420/580083). I would suggest you to avoid reference members until you know very well what you are doing. – Daniel Langr Feb 20 '23 at 12:43
  • 2
    Are you expecting `T &&` to be a forwarding reference? Those only apply to function parameters not member variables – Alan Birtles Feb 20 '23 at 12:56
  • 1
    its not quite clear what exactly you refer to when you say "It is an undefined behavior". Instantiating the template is ok, the type of the member is well defined. What would be UB (I am not sure either) is this line `std::cout << t_by_value.val << " " << t_by_ref.val << "\n";`. All the rest of the code is completely fine. I think one issue is that you are mixing up two things: reference collapsing and lifetime extension of a temporary when bound to a reference. For the latter, the template is irrelevant, while for there former nothing is undefined in your code – 463035818_is_not_an_ai Feb 20 '23 at 13:15
  • 1
    the code has no UB, it's valid, see [this answer](https://stackoverflow.com/a/75184490/5980430) which also contains a [quote to standard](https://timsong-cpp.github.io/cppwp/n4861/dcl.init#17.6.2.2) – apple apple Feb 20 '23 at 16:40

1 Answers1

3

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&&
The Dreams Wind
  • 8,416
  • 2
  • 19
  • 49
  • 1
    `t_by_value.val` is actually valid. – apple apple Feb 20 '23 at 16:21
  • @appleapple Yep. This answer is wrong on multiple fronts. s/b deleted or rewritten. – doug Feb 20 '23 at 17:41
  • @appleapple could you please elaborate how it could be valid here? i can't see any way for the temporary to survive past the constructor expression – The Dreams Wind Feb 20 '23 at 18:55
  • 2
    @TheDreamsWind you can see [this standard example](https://timsong-cpp.github.io/cppwp/n4861/dcl.init#17.6.2.2) (link from [this answer](https://stackoverflow.com/questions/75184355/does-a-constant-reference-member-variable-in-an-anonymous-struct-extend-the-life/75184490#75184490), change to n4861 (c++20)) – apple apple Feb 20 '23 at 19:08
  • 1
    basically temporary lifetime extension is applied, and it's because *no constructor* is involved here. – apple apple Feb 20 '23 at 19:11
  • 1
    Thanks for sticking with this! All looks good now. – doug Feb 20 '23 at 21:56
  • 1
    @TheDreamsWind it's works all the way back, not new to c++20, [see c++11 for example](https://timsong-cpp.github.io/cppwp/n3337/class.temporary#5). – apple apple Feb 21 '23 at 14:53
  • 1
    @appleapple nice catch. Removed the C++20 part – The Dreams Wind Feb 21 '23 at 18:40