[basic.lval] p5 states:
The result of a glvalue is the entity denoted by the expression.
I have a few questions regarding this and it's implications:
What is the entity denoted by the expression?
Would this differ from the result?
[expr.type] p1 states:
The expression designates the object or function denoted by the reference
So for example, given the declaration foo f; where foo is a class type, would the expression f; also be an identifier, and that's what it means by the expression denoting an entity, since the expressions name corresponds to the identifier?
Pretty much what I'm asking is, what does it mean for an expression to denote an entity, and how does it differ from the result?
I suppose that [basic.lval]/5 basically say that the concept of naming/denoting/designating and "result of" are used interchangeably in the standard.
The result of a glvalue is the entity denoted by the expression.
The result of a prvalue is the value that the expression stores into its context; a prvalue that has type cv void has no result.
A prvalue whose result is the value V is sometimes said to have or name the value V.
A glvalue or a prvalues are expressions [basic.lval]/1. All expressions are evaluated. Even a single name appearing in a expression (not in a declaration), is an expression. Before evaluation an expression may denote an entity and after evaluation the expression result in this entity.
For example, to evaluate the expression a+b, the virtual machine first evaluate a. a denotes some object, the result of the evaluation of a is this object. The same for b. Then the result of the expression a and the result of the expression b will be used (after some conversions) as operands in the evaluation of the addition.
Only the result of an expression is used by other expressions. So it seems natural to conflate an expression for its result. When we do that we forget that the result is the consequence of the evaluation of the expression.
Related
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.
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.
[dcl.constexpr] p10 sentence 3 says:
In any constexpr variable declaration, the full-expression of the initialization shall be a constant expression
However, in this declaration:
constexpr int a = 10;
constexpr int b = a;
a is not a constant expression, as it is glvalue core constant expression, but not a permitted result of a constant expression because it does not have static storage duration, and is not a temporary object.
However, with the application of an lvalue-to-rvalue conversion, it will become a constant expression. So does that mean that the initializer does not need to be a constant expression, and only the final result after conversions has to be?
In the link you quoted, see point 10:
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:
Your question focused on "permitted result of a constant expression" under the glvalue branch; however in your example it is the other branch "prvalue core constant expression" that applies. This can apply because of [conv.lval]/1,
A glvalue of a non-function, non-array type T can be converted to a prvalue
I agree it is a bit confusing that by "prvalue core constant expression" here they include the case of the result of lvalue-to-rvalue conversion on glvalues that meet the criteria; whereas in some other places in the standard "prvalue" excludes that case.
The word "full-expression" is a defined term. Notably ([intro.execution]/5)
Conversions applied to the result of an expression in order to satisfy the requirements of the language construct in which the expression appears are also considered to be part of the full-expression.
So yes, since the requirement says "the full-expression of the initialization shall be a constant expression", it means only the full-expression (which includes the conversion), not anything else, is required to be a constant expression.
The expression b in this code shall be a core constant expression
int main()
{
constexpr int a = 10;
const int &b = a;
constexpr int c = b; // Here
return 0;
}
since the standard says (8.20, paragraph 2 [expr.const] in n4700)
An expression e is a core constant expression unless the evaluation of
e would evaluate one of the following expressions:
...
an lvalue-to-rvalue conversion (7.1) unless it is applied to
...
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
First, the expression b in the above code is an lvalue (which is also a glvalue) since it's a reference, thereby being a variable (8.1.4.1, paragraph 1
[expr.prim.id.unqual]):
The expression is an lvalue if the entity is a function,
variable, or data member and a prvalue otherwise; it is a bit-field if the identifier designates a bit-field (11.5).
Second, the object the variable b denotes is a, and it's declared with constexpr. However, GCC complains:
./hello.cpp: In function ‘int main()’:
./hello.cpp:6:20: error: the value of ‘b’ is not usable in a constant expression
constexpr int c = b;
^
./hello.cpp:5:13: note: ‘b’ was not declared ‘constexpr’
const int &b = a;
As far as I can tell, a reference is not an object, so the above bullet apparently suggests that a shall be declared with constexpr. Am I missing something?
The reason why I don't agree with GCC is that GCC sees b as an object, thereby requiring it to be declared with constexpr. However, b is not an object!
One of the rules for core constant expressions is that we can't evaluate:
an id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization and either
it is initialized with a constant expression or
its lifetime began within the evaluation of e;
b is an id-expression that refers to a variable of reference type with preceding initialization. However, it is initialized from a. Is a a constant expression? From [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: [... ]
An entity is a permitted result of a constant expression if it is an object with static storage duration that is either not a temporary object or is a temporary object whose value satisfies the above constraints, or it is a function.
a is a glvalue core constant expression (it doesn't hit any of the restrictions in expr.const/2), however it is not an object with static storage duration. Nor is it a function.
Hence, a is not a constant expression. And b, as a result, isn't initialized from a constant expression and so can't be used in a core constant expression. And thus c's initialization is ill-formed as not being a constant expression. Declare a as a static constexpr int, and both GCC and Clang accept the program.
C++, you magical beast.
All quotes given in this answer are from the current working draft.
Given your example:
int main()
{
constexpr int a = 10;
const int &b = a;
constexpr int c = b; // here
return 0;
}
First of all, let's take your example line by line. The first line is a definition of a constexpr variable named by identifier a:
constexpr int a = 10;
The initializer 10 is an integral constant expression, hence it's a core constant expression, per [expr.const]/9:
An integral constant expression is an expression of integral or
unscoped enumeration type, implicitly converted to a prvalue, where
the converted expression is a core constant expression. [ Note: Such
expressions may be used as bit-field lengths, as enumerator
initializers if the underlying type is not fixed, and as alignments.
— end note ]
And it's a constant expression because it's a prvalue core constant expression that satisfies the [expr.const]/12 constraints:
A constant expression is [..] a prvalue core constant expression
whose value satisfies the following constraints:
(12.1) if the value is an object of class type, each non-static data
member of reference type refers to an entity that is a permitted
result of a constant expression,
(12.2) if the value is of pointer
type, it contains the address of an object with static storage
duration, the address past the end of such an object ([expr.add]), the
address of a non-immediate function, or a null pointer value
(12.3)
if the value is of pointer-to-member-function type, it does not
designate an immediate function, and
(12.4) if the value is an object
of class or array type, each subobject satisfies these constraints for
the value.
Hence, the initializer expression 10 is a prvalue core constant expression that's neither a pointer type nor a class type nor an array type. Hence, it's a constant expression.
The second line declares/defines a reference to a named by identifier b:
const int &b = a;
In order to know whether the initializer expression a is a core constant expression, you have to invoke [expr.const]/5:
An expression E is a core constant expression unless the evaluation
of E, following the rules of the abstract machine ([intro.execution]),
would evaluate one of the following: [..]
At this point, the evaluation of E does not evaluate any of the constraints defined in [expr.const]/5. Therefore, the expression E is a core constant expression.
But being E is a glvalue core constant expression that doesn't mean that E is also constant expression; so you have to check [expr.const]/12:
A constant expression is either a glvalue core constant expression
that refers to an entity that is a permitted result of a constant
expression [..]
An entity is a permitted result of a constant expression if it is an
object with static storage duration that either is not a temporary
object or is a temporary object whose value satisfies the above
constraints, or if it is a non-immediate function.
So the entity a is not a permitted result of constant expression because that entity neither refers to an object with static storage duration nor a temporary object or non-immediate function. Therefore, the glvalue core constant expression a is not a constant expression.
The third line defines an constexpr variable named by identifier c initialized by the variable b:
constexpr int c = b;
Again you have to invoke [expr.const]/5 in order to know whether the expression b is a core constant expression or not. As you already noticed, the bullet (5.8) maybe satisfied since the initialization of c involves an lvalue-to-rvalue conversion. So per [expr.const]/5
An expression E is a core constant expression unless the evaluation of
E [..] would evaluate one of the following:
(5.8) an lvalue-to-rvalue conversion unless it is applied to
(5.8.1) a non-volatile glvalue that refers to an object that is usable in constant expressions, or
(5.8.2) a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of E;
Note that the document to which I refer doesn't have the following bullet. But C++20 documents have it. And I don't know why it's removed from the current working draft. ([expr.const]/(5.12)):
(5.12) an id-expression that refers to a variable or data member of
reference type unless the reference has a preceding initialization and
either
(5.12.1) it is usable in constant expressions or
(5.12.2) its
lifetime began within the evaluation of E;
Even if it's checked, the expression E is still not a core constant expression because the reference is not usable in constant expressions (see below) and its lifetime doesn't begin within the evaluation of E.
Back to our explanation. The bullet (5.8) is satisfied since the expression E evaluates an lvalue-to-rvalue conversion. So (5.8.1) is tried first.
Indeed, the lvalue-to-rvalue conversion is applied to a non-volatile glvalue (b). But, Is this glvalue refers to an object that's usable in constant expressions? First, you have to check whether it's a constant-initialized variable, then also you have to check whether it's a potentially-constant variable, so [expr.const]/2 is checked first:
A variable or temporary object o is constant-initialized if
(2.1) either it has an initializer or its default-initialization results in some initialization being performed, and
(2.2) the full-expression of its initialization is a constant expression when interpreted as a constant-expression [..]
The first bullet (2.1) is satisfied because the variable b has an initializer. But the second bullet isn't satisfied because the full-expression of its initialization is not a constant expression because its initializer a is not a constant expression as aforementioned. Therefore, the variable b is not constant-initialized.
Just for completeness, the [expr.const]/3 is tried:
A variable is potentially-constant if it is constexpr or it has a
reference or const-qualified integral or enumeration type.
Since the variable b has a reference type, it's considered to be potentially-constant. Note, there's no need to check whether the variable b is potentially-constant because intuitively you can't even enter the [expr.const]/4 since it requires the variable to be constant-initialized:
A constant-initialized potentially-constant variable is usable in
constant expressions at a point P if its initializing declaration D is
reachable from P [..]
(emphasis mine)
Even though the variable is potentially-constant, it's not usable in a constant expression because it's not constant-initialized. So we ended up with that the variable b is not usable in constant expressions (as the GCC diagnostic hints to you). Therefore, the bullet (5.8.1) is not satisfied: Hence, the expression E is not a core constant expression and therefore not a constant expression.
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.