Is it a defect in basic.def.odr section - c++

struct S { static const int x = 0; };
int main(){
S obj;
int v = obj.x;
}
Consider the above code, Is obj.x an odr-use? According to look at the section of basic.def.odr#3
A variable x whose name appears as a potentially-evaluated expression ex is odr-used by ex unless applying the lvalue-to-rvalue conversion to x yields a constant expression 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 is applied to e, or e is a discarded-value expression.
We have such analysis as the following:
In my code, the variable name is x, which is the x in the above quote, and the expression obj.x is the ex in the quote. So, Is obj.x a potentially-evaluated expression? Yes, It is, because of this:
An expression is potentially evaluated unless it is an unevaluated operand or a subexpression thereof.
obj.x is neither an unevaluated operand or a subexpression thereof. So obj.x is a potentially-evaluated expression. And applying the lvalue-to-rvalue x indeed yields a constant expression because it's initialized by 0. What I doubt is the following, that is,
and, if x is an object, ex is an element of the set of potential results of an expression e
we assume the e is obj.x because lvalue-to-rvalue conversion will apply to it. And what's the set of potential results of e? As following:
basic.def.odr#2
The set of potential results of an expression e is defined as follows:
If e is a class member access expression, the set contains the potential results of the object expression.
Because e is a class member access expression. hence its set of potential results is object expression.
expr.ref#3
Abbreviating postfix-expression.id-expression as E1.E2, E1 is called the object expression.
So, the object expression here is obj, hence the set of potential results of expression obj.x contains obj. Obviously, here ex is xand if e isobj.xthen its set of potential results of expression would beobj. So, what the expression eof which the exx` is a element of set of potential results? According to look at basic.def.odr#2, I find nothing.
I only pretty sure the lvalue-to-rvalue conversion is applied to obj.x whole expression. However, all compiler all agree the use of obj.x is none odr-use. Is it a defect in the standard?

You can’t rely on a compiler to tell you whether it’s an odr-use; if it is, and you haven’t defined the variable, the program is ill-formed, no diagnostic required. That said, it seems like this was a defect in C++17 in that the possibility of referring to a static member variable with a class member access (as opposed to a qualified-id) was overlooked.

Related

what situation will the evaluation for expressions occur in

constexpr int func(int rf){
constexpr int v = rf; // #1
return 0;
}
int main(){
}
Consider the above code, the compiler complains such a code is ill-formed. The outcome is here:
error: 'rf' is not a constant expression
That is said, the expression at the place that marked with #1 is evaluated by the compiler. I agree that rf is not a constant expression, because it does violate the following rules:
An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine, would evaluate one of the following expressions:
expr.const#2
an lvalue-to-rvalue conversion unless it is applied to
a non-volatile glvalue of integral or enumeration type that refers to a complete non-volatile const object with a preceding initialization, initialized with a constant expression, or
a non-volatile glvalue that refers to a subobject of a string literal, or
a non-volatile glvalue that refers to a non-volatile object defined with constexpr, or that refers to a non-mutable subobject of such an object, or
a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of e;
However, what makes me confused is, I didn't call function func anywhere in my example, why the compiler evaluate the expression rf? That makes me don't understand what is evaluation and what is the execution.
According to this rule:
intro.execution#18
When calling a function (whether or not the function is inline), every value computation and side effect associated with any argument expression, or with the postfix expression designating the called function, is sequenced before execution of every expression or statement in the body of the called function. For each function invocation F, for every evaluation A that occurs within F and every evaluation B that does not occur within F but is evaluated on the same thread and as part of the same signal handler (if any), either A is sequenced before B or B is sequenced before A.
It sounds like that, only if the corresponding function is called, then the evaluation for expression which is in the function body does occur.
However, Obviously I didn't call func in my example. So, my questions are:
Question 1:
what situation will the evaluation for expressions occur in?
For constant expressions, there is only a crude tip in the standard, that is, [Note: Constant expressions can be evaluated during translation. — end note], there's no more.
Question 2:
As a contrast, If the statement at #1 would be int v = rf;, Does the compiler evaluate such an expression rf during translation if I don't call function func?
Question 3:
what's the difference between evaluation and execution?
Question 4:
where's the relevant clause in the standard specified what situation the evaluation for expressions will occur in?
the error comes from
constexpr int v = rf; // #1
you are trying to make a constexpr equal to something that is not. The constexpr is evaluated at compile time but rf is calculated only at run time. That means that the compiler will try to make v equal to something that was not calculated yet.
I will assume that you already know that, the full-expression of the initialization of a constexpr variable V has to be a constant expression ([dcl.constexpr]/10) so that the compiler, at compile-time, can replace each occurrence of expression V with its corresponding value.
And in order to know whether an expression is a constant expression, you have to firstly know whether the expression is a core constant expression. The standard has a whole subclause described in [expr.const] defines how to know whether an expression is a core constant expression.
From my point of view, any expression E is a core constant expression unless E evaluates any of the constraints in [expr.const]/5.
Considering what has been said, since your variable v is a constexpr, its initializer (rf in this case) has to be a constant expression. Ask yourself first: Is rf a core constant expression? To know that, ask yourself again: Is rf evaluates any of [expr.const]/5? In your case, it does evaluate lvalue-to-rvalue conversion (5.8) and neither (5.8.1) nor (5.8.2) is satisfied.
That's, The lvalue-to-rvalue conversion is applied to the non-volatile glvalue rf, but the expression rf is neither usable in a constant expression (because it is not potentially-constant constant-initialized or constexpr) nor refers to an object whose lifetime began within the evaluation of expression rf (because the lifetime of the variable rf began before the evaluation of the expression rf). Hence, the expression rf is not a core constant expression and therefore not a constant expression (can't be used as an expression anywhere when a constant expression is required)
Also, note that both [expr.const]/(5.10) and [expr.const]/(5.12) have not been reached yet: (5.8) is tried first.

Why doesn't the linker emit an error in the code below?

I found the example below here. Clearly the comment in the snippet was wrong as the variable S::x is odr-used by the expression &S::x.
struct S { static const int x = 1; };
void f() { &S::x; } // discarded-value expression does not odr-use S::x
int main(){
f();
}
See live example
I understand the compiler doesn't need to emit such an error because [basic.def.odr]/10 says "no diagnostic required". But why doesn't the linker emit an error about the undefined variable S::x, as it does in the code below?
#include<iostream>
struct S { static const int x = 1; } s;
int main() {
std::cout << &s.x << '\n';
}
See live example.
But why doesn't the linker emit an error about the undefined variable S::x, as it does in the code below?
Because it is simply optimized out! An expression which result is never used and has no side effect will simply be ignored. And what was ignored must not be linked in. There is simply no code which reference the variable, also if the address of it was taken but then is not used.
As seen in your wandbox example, the compiler emits the correct diagnostic:
"expression result unused".
Code which is not in use will not result later in linker errors ;)
Your second example uses the value ( address of var ) and so there is a need to evaluate the expression. That emits code to the linker where the symbol of the address can not be found anywhere.
You ask:
[Why] doesn't the linker emit an error?
While saying:
[basic.def.odr]/10 says "no diagnostic required"
You answer your own question in "no diagnostic required". Not complying to the odr-rule is Undefined Behavior, the linker could raise an error or build a 4D version of Tetris and it still would be OK by the specs!
And to clarify about &S::x odr-using or not x:
A variable x whose name appears as a potentially-evaluated expression ex is odr-used by ex unless applying the lvalue-to-rvalue conversion to x yields a constant expression 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 is applied to e, or e is a discarded-value expression.
The address of an object is never a constant expression. S::x is odr-used in &S::x.
To justify that last assertion:
[expr.const]/6
A constant expression is either a glvalue core constant expression that refers to an entity that is a permitted result of a constant expression (as defined below), or a prvalue core constant expression whose value satisfies the following constraints [...]
and
[expr.const]/2.7
2) An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine, would evaluate one of the following expressions:
[...]
2.7) an lvalue-to-rvalue conversion unless it is applied to
(none of the followinf points applies:)
2.7.1) a non-volatile glvalue of integral or enumeration type that refers to a complete non-volatile const object with a preceding initialization, initialized with a constant expression, or
2.7.2)
a non-volatile glvalue that refers to a subobject of a string literal, or
2.7.3)
a non-volatile glvalue that refers to a non-volatile object defined with constexpr, or that refers to a non-mutable subobject of such an object, or
2.7.4)
a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of e;
Clearly the comment in the snippet was wrong as the variable S::x is odr-used by the expression &S::x.
And then that expression is dropped on the bit floor. &S::x; is not ODR-used. Once variant of [basic.def.odr] paragraph 3 reads (bold emphasis mine):
A variable x whose name appears as a potentially-evaluated expression ex is odr-used by ex unless applying the lvalue-to-rvalue conversion to x yields a constant expression 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 is applied to e, or e is a discarded-value expression.
The section[expr] paragraph 11 covers the concept of a discarded-value expression:
In some contexts, an expression only appears for its side effects. Such an expression is called a discarded-value expression. The expression is evaluated and its value is discarded. The array-to-pointer and function- to-pointer standard conversions are not applied. The lvalue-to-rvalue conversion is applied if and only if the expression is a glvalue of volatile-qualified type and it is one of the following:
( expression ), where expression is one of these expressions,
id-expression,
subscripting,
class member access,
indirection,
pointer-to-member operation,
conditional expression where both the second and the third operands are one of these expressions, or
comma expression where the right operand is one of these expressions.
The statement &S::x; is a discarded-value expression that does not require lvalue-to-rvalue conversion, so there's nothing to convert. The statement might as well not exist, and hence doesn't exist as far as ODR-usage is concerned.
This metric can be changed by qualifying S::x as volatile rather than as const, and by trying to drop S::x on the bit floor:
struct S { static volatile int x; };
void f() { S::x; } // This discarded-value expression does odr-use S::x
int main(){
f();
}
The above compiles (but with a warning about a discarded expression) with GNU's C++ compiler/linker using --std=c++03 but fails to compile/link using --std=c++11 or higher. C++11 added quite a bit regarding the workings of the C++ abstract machine. Even though S::x; remains a discarded value expression, that S::x is now volatile requires the compiler to apply lvalue-to-rvalue conversion prior to dropping the result on the bit floor.
Change the S::x in the above to &S::x and the program once again compiles (but once again with a warning about a discarded expression). Even though S::x is volatile, it's address is not.

Why is S::x not odr-used?

Consider this example from cppreference:
struct S { static const int x = 1; };
void f() { &S::x; } // discarded-value expression does not odr-use S::x
I agree that &S::x is a discarded-value expression, since the standard says (9.2, paragraph 1 [stmt.expr] from n4700)
Expression statements have the form
expression-statement:
expression_opt ;
The expression is a discarded-value expression (Clause 8)...
However, is that enough for S::x to not be odr-used? 6.2, paragraph 3 [basic.def.odr] states
A variable x whose name appears as a potentially-evaluated expression ex is odr-used by ex unless
...
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 (7.1) is applied to e, or
e is a discarded-value expression (Clause 8).
The problem is that the discarded-value expression &S::x has no potential results (which means that S::x is not a potential result of &S::x), as you can see from 6.2, paragraph 2 [basic.def.odr]:
... The set of potential results of an expression e is defined as follows:
If e is an id-expression (8.1.4), the set contains only e.
If e is a subscripting operation (8.2.1) with an array operand, the set contains the potential results of that operand.
...
Otherwise, the set is empty.
Then, how can you explain that S::x is not odr-used?
It is indeed odr-used. Your analysis is correct (and I fixed that example a while ago).
Yes, in the example, &S::x odr-uses S::x.
[basic.def.odr]/4
A variable x whose name appears as a potentially-evaluated expression ex is odr-used by ex unless applying the lvalue-to-rvalue conversion to x yields a constant expression 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 is applied to e, or e is a discarded-value expression.
The address of an object is never a constant expression. That's why S::x is odr-used in &S::x.
To justify that last assertion:
[expr.const]/6
A constant expression is either a glvalue core constant expression that refers to an entity that is a permitted result of a constant expression (as defined below), or a prvalue core constant expression whose value satisfies the following constraints [...]
and
[expr.const]/2.7
2) An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine, would evaluate one of the following expressions:
[...]
2.7) an lvalue-to-rvalue conversion unless it is applied to
(none of the following points applies:)
2.7.1) a non-volatile glvalue of integral or enumeration type that refers to a complete non-volatile const object with a preceding initialization, initialized with a constant expression, or
2.7.2)
a non-volatile glvalue that refers to a subobject of a string literal, or
2.7.3)
a non-volatile glvalue that refers to a non-volatile object defined with constexpr, or that refers to a non-mutable subobject of such an object, or
2.7.4)
a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of e;
When declaring const int, it may be entirely discarded by the compiler unless you using its address. taking the address is not enough.
Discarded is not mean the value is not evaluated, it is, but it mean there is no memory address containing the const value, the compiler just replace the const variable by its value, as it was just a macro.
In addition, when taking a pointer to it and taking back the value from the pointer, doesn't impress the compiler much, it just ignores it and use the value.
The following code shows it, this code can be compiled and run (I test it by several compilers, I'm still not sure if it successfully compiled by all...) despite of the fact that S::x was not declared:
#include <iostream>
using namespace std;
struct S
{
static const int x=0;
};
//const int S::x; //discarded
int main()
{
const int *px = &S::x; //taking the address
cout<< *px <<endl; //print the value - OK
return 0;
}
But if I'll try to use the address itself(not the value) like:
cout<< px <<endl; //print the address - Will be failed
the link will failed dou to: "undefined reference to S::x".
Therefore, my conclusion is: taking an address without using it, doesn't count at all.

How does odr-use apply to a variable identified by a qualified-id?

Sample code:
struct S
{
static const int a = 0;
};
int const *b = &S::a;
My understanding of the intent of the ODR is that S::a should be odr-used here, because its address is taken.
My question is: Where and how does the Standard specify that S::a is odr-used in this code?
My research so far: The only relevant part of the C++14 standard seems to be [basic.def.odr]/3:
A variable x whose name appears as a potentially-evaluated expression ex is odr-used by ex unless applying the lvalue-to-rvalue conversion to x yields a constant expression 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 is applied to e, or e is a discarded-value expression.
However I don't see how S::a fits into this. The definition of name ([basic]/4) excludes qualified-ids, so x can't refer to S::a here. I don't see any other text in the standard supporting the use of name to mean anything other than the definition from [basic]/4.
Maybe x means a; however a never appears as an expression, let alone a potentially-evaluated one: the a in S::a is an identifier, not an expression. (A qualified-id is a nested-name-specifier followed by an identifier, or some other things not relevant here).
I also don't understand why two different placeholders x and ex are used in this sentence. Saying "A appears as B" says to me that A and B are lexically identical but seeing A in the context of B has some extra meaning. At first I thought this might mean x is a more abstract way of talking about a variable (e.g. meaning the variable that is identified by S::a) and ex is an expression denoting that variable. However the quote goes on to say "Applying the lvalue-to-rvalue conversion to x", therefore x is actually an expression after all; so I don't see what the difference is between x and ex.
Further, I am not sure what "an expression e" refers to exactly. I guess it is an existential taken over all expressions in the program such that ex is a subexpression of e.

When is a variable odr-used in C++14?

The C++14 draft (N3936) states in §3.2/3:
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 (Clause 5).
This doesn't make any sense to me: If an expression e is a discarded-value expression depends on the context, in which e is used. Every expression used in an expression-statement (§6.2) is a discarded-value expression. If the lvalue-to-rvalue conversion is applied to e also depends on the context e is used in.
Moreover, what does it mean for an expression to be in the set of potential results of another expression. One needs a notion of equality of expressions to be able to determine membership of a set. But we don't have referential transparency, so I cannot see how this could be achieved.
Why was this changed from C++11 to C++14? And how should this be interpreted? As it stands, it doesn't make sense.
The purpose of odr-use
Informally, odr-use of a variable means the following:
If any expression anywhere in the program takes the address of or binds a reference directly to an object, this object must be defined.
Clarification in the latest draft
In the latest version of the spec §3.2 has been clarified (see Draft C++14 on GitHub):
2 An expression is potentially evaluated unless it is an unevaluated operand (Clause 5) or a subexpression thereof. The set of potential results of an expression e is defined as follows:
If e is an id-expression (5.1.1), the set contains only e.
If e is a class member access expression (5.2.5), the set contains the potential results of the object expression.
If e is a pointer-to-member expression (5.5) whose second operand is a constant expression, the set contains the potential results of the object expression.
If e has the form (e1), the set contains the potential results of e1.
If e is a glvalue conditional expression (5.16), the set is the union of the sets of potential results of the second and third operands.
If e is a comma expression (5.18), the set contains the potential results of the right operand.
Otherwise, the set is empty.
[ Note: This set is a (possibly-empty) set of id-expressions, each of which is either e or a subexpression of e.
[ Example: In the following example, the set of potential results of the initializer of n contains the first S::x subexpression, but not the second S::x subexpression.
struct S { static const int x = 0; };
const int &f(const int &r);
int n = b ? (1, S::x) // S::x is not odr-used here
: f(S::x); // S::x is odr-used here, so
// a definition is required
—end example ] —end note ]
3 A variable x whose name appears as a potentially-evaluated expression ex is odr-used by ex unless applying the lvalue-to-rvalue conversion (4.1) to x yields a constant expression (5.19) that does not invoke any nontrivial 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 (Clause 5).
What was the situation in C++11?
§3.2/2 in C++11 reads:
An expression is potentially evaluated unless it is an unevaluated operand (Clause 5) or a subexpression thereof. A variable whose name appears as a potentially-evaluated expression is odr-used unless it is an object that satisfies the requirements for appearing in a constant expression (5.19) and the lvalue-to-rvalue conversion (4.1) is immediately applied.
The problem with these wordings was DR 712. Consider this example:
struct S {
static const int a = 1;
static const int b = 2;
};
int f(bool x) {
return x ? S::a : S::b;
}
Since S::a and S::b are lvalues the conditional expression x ? S::a : S::b is also an lvalue. This means that the lvalue-to-rvalue conversion is not immediately applied to S::a and S::b, but to the result of the conditional expression. This means that by the wording of C++11, these static data members are odr-used and a definition is required. But actually only the values are used, hence it is not neccessary to define the static data members - a declaration would suffices. The new wording of draft C++14 solves this.
Does the new wording resolve all issues?
No. In the following example the variable S::a is still odr-used:
struct S { static constexpr int a[2] = {0, 1}; };
void f() {
auto x = S::a[0];
}
Hence I submitted a new issue to add the following bullet to §3.2/2:
if e is a glvalue subscripting expression (5.2.1) of the form E1[E2], the set contains the potential results of E1.