Understanding the example on lvalue-to-rvalue conversion
Asked Answered
L

1

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
Loydloydie answered 13/2, 2015 at 18:34 Comment(5)
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.Bewilder
Where did you find this example? In the Standard, or another source?Owsley
@BenVoigt It's from C++14 [conv.lval]/2.2.Caparison
@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.Donettedoney
@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.Owsley
D
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 .

Donettedoney answered 13/2, 2015 at 18:41 Comment(2)
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)?Loydloydie
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.Lumbricalis

© 2022 - 2024 — McMap. All rights reserved.