C++ - using const reference to prolong a member of a temporary, ok or UB?

1.2k views Asked by At

consider something like this:

#include <iostream>

struct C {
    C(double x=0, double y=0): x(x) , y(y) {
        std::cout << "C ctor " << x << " " <<y << " "  << "\n";
    }
    double x, y;
};

struct B {
    B(double x=0, double y=0): x(x), y(y) {}
    double x, y;
};

struct A {
    B b[12];

    A() {
        b[2] = B(2.5, 14);
        b[4] = B(56.32,11.99);
    }
};


int main() {
    const B& b = A().b[4];
    C c(b.x, b.y);
}

when I compile with -O0 I'm getting the print

C ctor 56.32 11.99

but when I compile with -O2 I'm getting

 C ctor 0 0

I know we can use const reference to prolong a local temporary, so something like

const A& a = A();
const B& b = a.b;

would be perfectly legal. but I'm struggling to find the reasoning for why the same mechanism/rule doesn't apply for any kind of temporary

EDIT FOR FUTURE REFERENCE:

I'm using gcc version 6.3.0

2

There are 2 answers

5
leslie.yao On BEST ANSWER

Your code should be well-formed, because for temporaries

(emphasis mine)

Whenever a reference is bound to a temporary or to a subobject thereof, the lifetime of the temporary is extended to match the lifetime of the reference

Given A().b[4], b[4] is the subobject of b and the data member b is the subobject of the temproray A(), whose lifetime should be extended.

LIVE on clang10 with -O2
LIVE on gcc10 with -O2

BTW: This seems to be a gcc's bug which has been fixed.

From the standard, [class.temporary]/6

The third context is when a reference is bound to a temporary object.36 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:

...

[ Example:

template<typename T> using id = T;

int i = 1;
int&& a = id<int[3]>{1, 2, 3}[i];          // temporary array has same lifetime as a
const int& b = static_cast<const int&>(0); // temporary int has same lifetime as b
int&& c = cond ? id<int[3]>{1, 2, 3}[i] : static_cast<int&&>(0);
                                           // exactly one of the two temporaries is lifetime-extended

— end example ]

2
robthebloke On

A().b[4] is not a temp or rvalue, which is why it does not work. Whilst A() is a temporary, you are creating a reference to an array element that exists at the point of creation. The dtor then triggers for A(), which means the later access to b.b becomes somewhat undefined behavior. You'd need to hold onto the A& to ensure b remains valid.

const A& a = A();
const B& b = a.b[4];
C c(b.x, b.y);