4

I have a hard time understanding how this code (an example from the C++14 draft standard [conv.lval]) invokes undefined behavior for g(false). Why does constexpr make the program valid?

Also, what does it mean by "does not access y.n"? In both calls to g() we are returning the n data member so why does the last line say it doesn't access it?

struct S { int n; };
auto f() {
    S x { 1 };
    constexpr S y { 2 };
    return [&](bool b) { return (b ? y : x).n; };
}
auto g = f();
int m = g(false); // undefined behavior due to access of x.n outside its
                  // lifetime
int n = g(true);  // OK, does not access y.n
Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
template boy
  • 10,230
  • 8
  • 61
  • 97
  • 1
    My guess is that `constexpr` is evaluated at compile time and is stored in some read-only section of the program independent of the stack frame while `x` is in the stack frame, and accessing it after the stack frame has been deleted is causing undefined behavior. – R Sahu Feb 13 '15 at 18:37
  • Where did you find this example? In the Standard, or another source? – Ben Voigt Feb 13 '15 at 19:33
  • 1
    @BenVoigt It's from C++14 [conv.lval]/2.2. – Casey Feb 13 '15 at 20:20
  • @BenVoigt your comments were very helpful. I actually just started digging into the ODR rules due to some recent SO questions w.r.t to constexpr and ODR that concerned me. The relatively new wording is not straight-forward and the few explanations out there are not great. – Shafik Yaghmour Feb 14 '15 at 02:49
  • @ShafikYaghmour: Yeah I really don't like the term "potential results of an expression" that has absolutely nothing to do with the "result" of that same expression. – Ben Voigt Feb 14 '15 at 02:51

1 Answers1

8

This is because y.n is not odr-used and therefore does not require an access to y.n the rules for odr-use are covered in 3.2 and says:

A variable x whose name appears as a potentially-evaluated expression ex is odr-used unless applying the lvalue-to-rvalue conversion (4.1) to x yields a constant expression (5.19) that does not invoke any non-trivial functions and, if x is an object, ex is an element of the set of potential results of an expression e, where either the lvalue-to-rvalue conversion (4.1) is applied to e, or e is a discarded-value expression

Note, Ben Voigt made some helpful comments that clarified this one a bit. So the working assumption here is that x would be:

y

and e would be(the different expression that e is defined for is covered in paragraph 2 of section 3.2):

(b ? y : x).n

y yields a constant expression and the lvalue-to-rvalue conversion is applied to the expression e.

Since f yields a lambda which captures f's local variables by reference x is no longer valid once the call to f is done since x is an automatic variable inside f. Since y is a constant expression it acts as if y.n was not accessed and therefore we don't have the same lifetime issue.

Your example is included in N3939 section 4.1 [conv.lval] and right before that example it says:

When an lvalue-to-rvalue conversion is applied to an expression e, and either

and includes the following bullet which the examle belongs to:

the evaluation of e results in the evaluation of a member ex of the set of potential results of e, and ex names a variable x that is not odr-used by ex (3.2),

then:

the value contained in the referenced object is not accessed

This was applied to the C++14 draft standard due to defect report 1773 .

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
  • What does it mean by "the value contained in the referenced object is not accessed"? `2` is the value of `y.n` so does that mean I don't get `2` back from `g(true)`? – template boy Feb 13 '15 at 18:44
  • 3
    You do get back 2 from `g(true)` but the program didn't have to access a physical memory location named `y.n` to get that value. – Khouri Giordano Feb 13 '15 at 18:50