As far as I understand, the [basic.def.odr]p3 tries to codify in
Standardese the following concept:
Objects are regions of storage and can contain values. Let x be an
object, and ex be an expression that names this object. If the compiler
knows the value stored within x at the point where ex is evaluated, it
does not need to read this value from the object. Therefore, if we only
use ex to read the value of x, the object (the region of storage) x does
not need to exist. Similarly, if the compiler knows that the value of ex
is discarded, the region of storage x does not need to exist.
This has been restricted to values known at compile-time; specifically
to values that all conforming compilers must be able to compute (know)
at compile-time. This restriction is probably for portability.
Another way to formulate the concept: If we do not need the address of
x, and if we do not need to store something within x, then we do not
need a region of storage (with a unique address).
There is an additional restriction: The value of x has to be read
"immediately" from ex. For example:
{
auto* p = &ex;
auto c = *p; // indirect read
}
I would guess this too is to keep things simple for implementers, and
therefore allow practical portability. Imagine a less restrictive rule,
where some compilers require a definition of x in the above example
because they're unable to determine that p is only used to read the
value of x. Consider a situation where the address of x is required:
auto* p = &ex;
std::cout << (void*)p; // unique address is guaranteed
Here, we do need the region of storage x for its unique address.
The lvalue-to-rvalue conversion reads the value of an object (lvalues
are always objects). So when the lvalue-to-rvalue conversion is applied
directly to ex, and ex is a compile-time constant, we do not need the
object x (we do not odr-use x). In other contexts, the object x can be
required. The definition of x is NOT required if it is NEVER odr-used.
If the l-t-r conv is not directly applied to ex, maybe it is applied to
an expression that ex is part of? Assume ex and ey are of type const int:
42 + (b ? ex : ey)
The l-t-r conversion is not applied directly to ex, but it is not very
hard for the compiler to see that in order to compute the value of the
complete expression, we only need the value of ex, but not the object x.
This is what CWG 712 is about: There are cases that are simple enough
that do not want to enforce the existence of a definition of x, even
though they do not immediately read the value of x via the expression ex.
These cases have been specified by looking at the (sub)expressions e
that contain ex. We start from ex and move towards the complete
expression c. If we can easily determine that e either discards the
value of x or only reads the value of x, then we're done: we know that
this occurrence of ex does not require an object x. Otherwise, we look
at the expression e' that e is part of, and ask again: can we easily
determine that e' reads from x or discard its value?
We will end up either at an expression that is too complicated (so we
require a definition), or we have already determined that we don't need
x here.
> I just can't see how
> can you reach this conclusion (your interpretation of the text), just
> by reading the Standard?
The Standard says: "ex is an element of the set of potential results of
an expression e [, where e fulfils the following criteria ...]"
If ex is an element of the set of potential results of ANY such
expression e - call it e' - than this condition is fulfilled:
ex is an element of the set of potential results of the expression e',
and e' fulfils the criteria
The Standard doesn't say "[...] of a complete expression e", so we are
potentially looking at subexpressions. Additionally, if ALL expressions
that ex is part of had to fulfil the criteria, then it should read "ex
is an element of the set of potential results of ALL expressions e it is
part of". So I concluded: just one such expression e is sufficient.
On the topic of initialization vs lvalue-to-rvalue conversion: Some time
ago (C++11), I would have said that some forms of initialization are
supposed to apply the lvalue-to-rvalue conversion, such as:
int x;
int y = x; // causes l-t-r
because the l-t-r at that time was the only place where it said that
reading from an uninitialized object causes Undefined Behaviour. But
with the resolution of CWG 1787, this has changed:
https://github.com/cplusplus/draft/commit/426888a7fdd7d8ee6aa1aef60f1f5e3526faaf6c
Now, the UB in the above example is introduced by _an evaluation that
produces an indeterminate value_. The lvalue-to-rvalue conversion is no
longer required for the UB. We do know from [dcl.init]p17.8 that we need
to extract the value from x, but as far as I know, the Standard does not
strictly require the lvalue-to-rvalue conversion to read the value from
an object.
This missing piece otherwise seemed to be purely a problem for
language-lawyers, but now it seems that [basic.def.odr]p3 requires this
missing piece in order to codify the concept I've mentioned above: if
these kinds of initialization do not require l-t-r conversion, then:
struct S {
constexpr static short M = 42;
};
int x = M; // odr-use of M
int y = 0 + M; // no odr-use of M
And this, to me, does not seem to be the intention of [basic.def.odr]p3.
Kind regards,
dyp
On 14.05.2015 17:11, Leon Trotski wrote:
> You wrote this and I think I understood what you meant.
>
> If x is an object, an additional condition must be fulfilled to
> avoid odr-use: Let S be the set of expressions where ex is a potential
> result of. That is, for every expression e in S, ex is a member of the set
> of potential results of e. If S contains an element e where either
> - the lvalue-to-rvalue conversion is applied to e, or
> - e is a discarded-value expression
> then x is not odr-used by ex.
>
> You're saying that if we can show *just one* element of the set S that
> satisfies one of the conditions above, then the object x is not odr-used
> (assuming of course that x satisfies the first condition stated in the
> paragraph, i.e., that it is a constant expression). I just can't see how
> can you reach this conclusion (your interpretation of the text), just by
> reading the Standard?
>
> But even if we accept this interpretation as valid, there's still the
> problem you mentioned on your first comment above, about the fact that the
> Standard doesn't seem to support the lvalue-to-rvalue conversion of the
> variable *i* in my first example when it's passed by-value to the function
> *f*, which is a necessary condition, according to your interpretation, for
> this variable to be considered not odr-used by the expression *f(i)*.
>