37

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

curiousguy
  • 8,038
  • 2
  • 40
  • 58
user2717954
  • 1,822
  • 2
  • 17
  • 28
  • I don't know which compiler/toolchain you use. I have tested this with C++2a + latest CLang (HEAD), and seems to work fine -> https://wandbox.org/permlink/CNRZzNSXlD4NQUNg and as you can see the command issued is: `clang++ prog.cc -Wall -Wextra -O2 -march=native -I/opt/wandbox/boost-1.71.0/clang-head/include -std=gnu++2a -pedantic` – mutantkeyboard Sep 10 '19 at 07:40
  • 1
    gcc 6.3.0 (which is the available version at my office) – user2717954 Sep 10 '19 at 07:44
  • 2
    @mutantkeyboard compiling without errors does only mean that it is syntactically correct. It does not mean that it is valid. And running _"without"_ errors does not mean that it is valid either, UB means that it could run without any error message producing the expected result, but it is still UB and therefore the program would not be valid. – t.niese Sep 10 '19 at 07:50
  • @t.niese Completely agree with you. It wasn't the point. I was more interested to see how the different compiler/toolchain behaves in this situation, since I found this to be somewhat interesting behaviour. That's why I asked him to give me the GCC/CLANG version :) I'm doing a bit of research on compiler internals, so this was kind of thing that was interesting thing to test. – mutantkeyboard Sep 10 '19 at 07:56
  • 1
    @mutantkeyboard but then `[...]and seems to work fine[...]` is really misleading as it implies that you think that it is valid, just because it compiles and you don't get any error message. The question can't be answered by testing if it compiles without any error message. (except if you know a compiler with compiler settings that would exactly wan about possible UB due to const ref). – t.niese Sep 10 '19 at 07:59

2 Answers2

34

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 ]

Community
  • 1
  • 1
songyuanyao
  • 169,198
  • 16
  • 310
  • 405
1

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);
Rabbid76
  • 202,892
  • 27
  • 131
  • 174
robthebloke
  • 9,331
  • 9
  • 12