I'm trying to understand the constant expression concept (from c++reference):
struct S {
static const int c;
};
const int d = 10 * S::c; // not a constant expression: S::c has no preceding
// initializer, this initialization happens after const
const int S::c = 5; // constant initialization, guaranteed to happen first
Why isn't the S::c a constant expression untill we define it. It was declared as a static const data member though...
Quoting relevant part of the C++11 standard (draft N3337), section 5.19, paragraph 2:
A conditional-expression is a core constant expression unless it involves one of the following as a potentially evaluated subexpression (3.2), but subexpressions of logical AND (5.14), logical OR (5.15), and conditional (5.16) operations that are not evaluated are not considered [ Note: An overloaded operator invokes a function. — end note ]:
an lvalue-to-rvalue conversion (4.1) unless it is applied to
a glvalue of integral or enumeration type that refers to a non-volatile const object with a preceding initialization, initialized with a constant expression
There is no preceding initialization of S::c in your definition of d.
Edit: why this applies:
5.1.1/8: S::c is an lvalue.
3.10/1: a glvalue is an lvalue or xvalue.
5/8: specifies that lvalue-to-rvalue conversion happens whenever an operator that expects a prvalue is used.
Proving that multiplication expects a prvalue is hurting my head. It is implied in many places, but I haven't found anywhere it is said explicitly.
In this sequence …
constexpr int d = 10 * S::c;
const int S::c = 5;
… the value of S::c is not known yet when the d value is compiled. But try to swap these lines:
const int S::c = 5;
constexpr int d = 10 * S::c;
Constant initialization is performed before other initialization in the C++ compiling process. In the example, the constant initialization of d is guaranteed to occur before the constant initialization of S::c. A constant expression must consist exclusively of constant values. When d is initialized, S::c is known to be a constant value, so the expression is not considered constant.
Related
This question already has answers here:
The question about a glvalue constant expression used in a context that requires a constant expression
(2 answers)
The value of a const variable is or is not usable in a constant expression, depending on the variable type
(2 answers)
Initialization of a constexpr variable
(2 answers)
Closed 5 months ago.
I have the following code that demonstrate my problem:
int main(void)
{
const int ci = 42;
constexpr int j = ci;
}
The above program compiles fines. But I'm expecting it to be ill-formed.
First, the initializer 42 is an integral constant expression converted to int via identity conversion; then, the converted expression is a core constant expression: ([const.expr]/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.
Second, I claim that the expression E = ci is not usable in constant expression because it's not constant-initialized (i.e, it's variable has no static duration) even though the expression ci is potentially-constant variable (i.e, it's variable of const-qualified integral type). So you can't apply the rule [expr.const]/4 because it requires the object to be potentially-constant as well as constant-initialized:
A constant-initialized potentially-constant variable V is usable in constant expressions at a point P if V's initializing declaration D
is reachable from P and ..
So per my understanding, for variable ci to be constant-initialized, it has to have a static duration as well as a constant-expression initializer.
Assuming my understanding is correct so far, I will continue.
In the initialization of j, an lvalue-to-rvalue conversion is performed on the glvalue ci; but this conversion is applied to a non-volatile glvalue that refers to an object ci that is not usable in constant expressions. So I'm expecting the program to be ill-formed because the expression E evaluates an lvalue-to-rvalue conversion and neither rule in [expr.const]/(5.9) permits it. Am I correct? What I'm missing/conflating here? Am I missing any wording?
As a sidenote, in C++17, the rule regarding this point is more restricted and clear at least for me:
an lvalue-to-rvalue conversion unless it is applied to
(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 [..]
It's definitely clear to me that the wording is applied here and (2.7.1) is satisfied. But the wording regarding this point specifically is changed in C++20: The term usable "usable in constant expressions" appears since C++20 per my search. Note that, if possible, I need a C++20 answer.
I claim that the expression E = ci is not usable in constant expression because it's not constant-initialized (i.e, it's variable has no static duration)
constant-initialized is defined in [expr.const]/2. It is not the same as "has constant initialization", and does not care about storage duration.
By the definition of "constant-initialized" ([expr.const]/2), the following is constant-initialized because 42 is a constant expression.
int ci = 42;
By the definition of "potentially-constant" ([expr.const]/3), the following is potentially-constant because the variable is a const-qualified integral type.
const int ci;
If you have both of these at function scope, then you have a variable that is usable in constant expressions from the point of definition until the end of the variable's scope (special case of [expr.const]/4).
The expression ci is constant expression. There's no need to request [expr.const]/5 to know whether the expression is core constant expression.
constexpr int func(int const& rf){
return rf;
}
int main(){
constexpr int value = func(0);
}
Consider the above code, the variable value shall be initialized by a constant expression, which is func(0), which firstly shall be a core constant expression. To determine whether the expression func(0)is a core constant expression, the following rules will be applied to it, that is:
expr.const#2.7
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:
an lvalue-to-rvalue conversion unless it is applied to
[...], 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;
Despite the lvalue-to-rvalue conversion is applied to rf and such conversion satisfied the bullet (2.7.4), however, take a look to the next paragraph, that is:
expr.const#2.11
an id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization and either
(2.11.1) it is initialized with a constant expression or,
(2.11.2) its lifetime began within the evaluation of e;
I don't know what actually the phrase preceding initialization mean? Does it mean that a variable should be initialized before using it, or it means that in a declaration of a variable shall have an initializer. Anyhow, before applying the lvalue-to-rvalue conversion to glvalue rf, the glvalue rf should be evaluated to determine the identity of an object, which is ruling by:
A glvalue is an expression whose evaluation determines the identity of an object, bit-field, or function.
That means not only bullet [expr.const#2.7] shall be obeyed but also [expr.const#2.11] shall be obeyed.
Because id-expression rf is of reference type. So, in order to make the expression func(0) be a core constant expression, the id-expression rf must have a preceding initialization, and satisfies at least one of bullet (2.11.1) and (2.11.2). In my example, the bullet (2.11.2) is obeyed by rf and the compiler agree that func(0) is a constant expression. The outcome is here, the outcome seems to evidence that preceding initialization means be initialized rather than have an initializer due to the parameter declaration does not have an initializer in the first example.
In order to check such a thought, I test the below code:
constexpr int func(int const& rf){
constexpr int const& v = rf;
return 0;
}
int main(){
static int const data = 0;
constexpr int value = func(data);
}
The outcomes of the compilers point out rf is not a constant expression. I'm confuse at this outcome. According to the above supposition. rf has a preceding initialization, and the bullet (2.11.1) is obeyed because data is a constant expression, even if the bullet (2.11.2) does not be satisfied.
So, I wonder what actually does the phrase preceding initialization mean? If it means that a declaration for a variable has an initializer, how could the expression func(0) in the first example be a constant expression?
The outcome seems to evidence that "preceding initialization" means "be initialized" rather than have an initializer due to the parameter declaration does not have an initializer in the first example.
It does mean "be initialized", but it's more importantly about the visibility of a preceding initialization within the context of the expression being evaluated. In your example, in the context of evaluating func(0) the compiler has the context to see the initialization of rf with 0. However, in the context of evaluating just the expression rf within func, it doesn't see an initialization of rf. The analysis is local, as in it doesn't analyze every call-site. This leads to expression rf itself within func not being a constant expression while func(0) is a constant expression.
If you were to write this instead:
constexpr int func(int const& rf) {
/* constexpr */ int const& v = rf;
return v;
}
int main() {
static int const data = 0;
constexpr int value = func(data);
}
This is okay again because in the context of func(data), rf has a preceding initialization with a constant expression data, and v has a preceding initialization with rf which is not a constant expression but its lifetime began within the evaluation of func(data).
This is CWG2186:
2186. Unclear point that “preceding initialization” must precede
Similar to the concern of issue 2166, the requirement of 8.20 [expr.const] bullet 2.7.1 for
— 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
does not specify the point at which the determination of “preceding initialization” is made: is it at the point at which the reference to the variable appears lexically, or is it the point at which the outermost constant evaluation occurs? There is implementation divergence on this point.
But the meaning should be "lexically", because ODR.
#include <iostream>
constexpr int func2(int const& id){
return id;
}
template<int v>
struct Test{
};
int main(){
const int v = 0;
Test<func2(v)> c;
}
Consider the above code,I just don't understand why the code is well-formed.My pointview is that the name v is used as a glvalue when evalute expression func2,becuase the parameter of func2 is of reference type,the v need to be bound to the id-expression id.So we look at the requirement of a glvalue constant expression,here are quotes about that.
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.
We ignore the case of prvalue,because here v is used as a glvalue.
An entity is a permitted result of a constant expression is:
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.
In my program portion,The const int v = 0; does not have static storage duration,it just has automatic storage duration.So when evaluting the expression func2(v) to determine whether it is a constant expression,Firstly,the v must be a glvalue core constant expression that refers to an entity that is a permitted result of a constant expression,therefore,why the program is well-formed here?If I lose any important quote,Please correct me.
We ignore the case of prvalue, because here v is used as a glvalue
Is it though? This is an example from cppreference:
void test() {
static const int a = std::random_device{}();
constexpr const int& ra = a; // OK: a is a glvalue constant expression
constexpr int ia = a; // Error: a is not a prvalue constant expression
const int b = 42;
constexpr const int& rb = b; // Error: b is not a glvalue constant expression
constexpr int ib = b; // OK: b is a prvalue constant expression
}
And yes, const int b = 42 is rather weird here because technically speaking, you can bind b to const int&, const_cast the const away and assign a runtime value to it. However, considering what is an integral constant expression and what are the requirements of a const object it makes perfect sense:
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. If an expression
of class type is used where an integral constant expression is
expected, the expression is contextually implicitly converted to an
integral or unscoped enumeration type.
Variable b sure does look like something you could implicitly convert to a prvalue constant expression because it basically serves as an alias for literal 42 in this context and integer literals are prvalues by definition.
Now for the problematic part - this:
const object - an object whose type is const-qualified, or a
non-mutable subobject of a const object. Such object cannot be
modified: attempt to do so directly is a compile-time error, and
attempt to do so indirectly (e.g., by modifying the const object
through a reference or pointer to non-const type) results in undefined
behavior.
And:
A core constant expression is any expression whose evaluation would
not evaluate any one of the following:
...
an expression whose evaluation leads to any form of core language
undefined behavior (including signed integer overflow, division by
zero, pointer arithmetic outside array bounds, etc). Whether standard
library undefined behavior is detected is unspecified.
Means that as soon as you start doing funny things with that b, you can expect anything to happen. For example, this is what I tried doing to your code in latest MSVC with all standard-conformance options switched on:
#include <iostream>
#include <random>
constexpr int func2(int const& id) {
return id;
}
template<int v>
struct Test {
long array[v];
};
int main() {
const int v = 0;
const int& ref = v;
const_cast<int&>(ref) = std::random_device()() % std::numeric_limits<int>::max();
Test<func2(v)> c;
return 0;
}
With language extensions turned on I got a C4200: nonstandard extension used : zero-sized array in struct/union warning. After switching them off, the program wouldn't compile. And when I deleted the array part from the struct it started compiling again.
I try to answer this question.why the func2(v) is a constant expression,Becuase For expression func2(v),when evaluting this postfix-expression,there's no requirement that v must be a glvalue constant expression in the list of "would evaluate one of the following expressions:",Even,these rules does not mandate that the one expression within a potentially core constant expression would be a glvalue constant expression,only require the expression does not violate the listed requirement.So let's continue,When initialization of the parameter,It's another rule here:
A full-expression is:
[...]
an init-declarator or a mem-initializer, including the constituent expressions of the initializer
So,when evalute this full-expression,it only does not violate these listed condition,then the func2(v) would be evaluted as a constant expression,So let's look at these rules:
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;
For id-expression id,its preceding initialization is the corresponding argument,Because of this rule:
When a function is called, each parameter ([dcl.fct]) shall be initialized ([dcl.init], [class.copy], [class.ctor]) with its corresponding argument.
So,the first condition is true.And "it is initialized with a constant expression" is false,the condition "its lifetime began within the evaluation of e" is true.In conlusion,the expression func2(v) indeed 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.
I read a little of CLang implementation of standard library and it confuses me a little bit on const and constexpr.
template<class _Tp, _Tp __v>
struct integral_constant
{
static constexpr _Tp value = __v;
};
template<class _Tp, _Tp __v>
const _Tp integral_constant<_Tp, __v>::value;
What makes me confusing is that, it is using constexpr inside class definition and const outside. My question is, is that allowed? And under what situation const and constexpr can be used interchangeably? Of course constexpr functions cannot apply to const, so I am talking about const data and constexpr data.
I did read some standard draft and the proposal in
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2235.pdf,
but it makes me feel more confusing. So I have some more questions,
In N2235, it clearly states that, const data are not guaranteed to be a compile time constants, see the following example,
struct S {
static const int size;
};
const int limit = 2 * S::size; // dynamic initialization
const int S::size = 256;
and constexpr is supposed to solve this, so at least under this situation, constexpr is not allowed as below,
struct S {
static const int size;
};
constexpr int limit = 2 * S::size; // shall be error in my understanding
const int S::size = 256;
However, after reading C++ standard draft N3225, I see nowhere explicitly stated that the above example shall cause an error. Particularly, from 7.1.5/9,
A constexpr specifier used in an
object declaration declares the object
as const. Such an object shall have
literal type and shall be initialized.
If it is initialized by a constructor
call, the constructor shall be a
constexpr constructor and every
argument to the constructor shall be a
constant expression. that call shall
be a constant expression (5.19).
Otherwise, every full-expression that
appears in its initializer shall be a
constant expression.
Therefore, if constexpr int limit = 2 * S::size; is invalid, then S::size must not be an constant expression, then from 5.19 (constant expression), I see nowhere the standard disallow 2 * S::size in the above example to not be a constant expression.
Can anybody point out anything I have overlooked? Thank you very much.
S::size is not a constant expression according to N3225 §5.19p2:
A conditional-expression is a constant expression unless it involves one of the following…
an lvalue-to-rvalue conversion (4.1) unless it is applied to
a glvalue of integral or enumeration type that refers to a non-volatile const object with a preceding initialization, initialized with a constant expression, or
[other conditions that don't apply]
Note how the second bullet point I quoted allows an integral static data member which is itself initialized with a constant expression to also be a constant expression, but your S::size is uninitialized.
(Side-note: constant-expressions are defined in terms of conditional-expressions because that's how the C++ grammar works.)
If you're wondering how the lvalue-to-rvalue conversion happens, see §5p9:
Whenever a glvalue expression appears as an operand of an operator that expects a prvalue for that operand, the lvalue-to-rvalue (4.1), array-to-pointer (4.2), or function-to-pointer (4.3) standard conversions are applied to convert the expression to a prvalue.
This is probably a good example of how reading the standard doesn't make a good reference, though there's not much else available yet for 0x.
"every full-expression that appears in it's initializer shall be a constant expression"
S::size is not a constant expression, therefore it can not appear in the initialization of a constant expression.