The following program compiles successfully with all major compilers:
struct S {
constexpr S(const S&){};
constexpr S() = default;
};
int main(void) {
S s1{};
constexpr S s2{ s1 };
}
The rule governing the initialization of constexpr variables is [dcl.constexpr]/10: (emphasis mine)
A constexpr specifier used in an object declaration declares the object as const. Such an object shall have a literal type and shall be initialized. In any constexpr variable declaration, the full-expression of the initialization shall be a constant expression (7.7). A constexpr variable shall have constant destruction.
Per the bold part, the full-expression of the initialization shall be a constant expression. Per my understanding, the full-expression here is the init-declarator per [into.execution]/5:
A full-expression is
[..] (5.4) an init-declarator ([dcl.decl]) [..]
Per the grammar of init-declarator, the init-declarator is a declarator followed by an initializer:
init-declarator:
declarator initializer
Given this information, we can conclude that the full-expression of the initialization constexpr S s2{ s1 }; is s2{ s1 } where s2 is a declarator and { s1 } is an initializer.
Now, [dcl.constexpr]/10 tells us that the full-expression (which is s2{ s1 } init-declarator) shall be a constant expression. And I've stucked at this point. Per [expr.const]/11, a constant expression is either a glvalue or prvalue core constant expression (with some additional constraints).
For the above example, I found myself reading [dcl.constexpr]/10 as: "the full-expression s2{ s1 } shall be a glvalue or prvalue core constant expression". So how the init-declarator s2{ s1 } can be a glvalue or prvalue core constant expression? As you can see, my interpretation led me to a wrong theory. So, What did I misread/conflate here?
Intuitively, I just see the init-declarator s2{ s1 } is a copy constructor call with s1 as an argument. But that is not the problem I'm trying to understand. Also, why the above program is well-formed is not what I'm trying to ask for. Instead, I need to know why my interpretation of this rule has not brought us to a reasonable conclusion.
EDIT
Note, the same problem we will encounter with this simple example (Demo):
int main(void) {
static const int i = 42;
constexpr int j = i;
constexpr const int &r = i;
}
The full-expression of the initialization constexpr int j = i; is the init-declarator j = i, where j is a declarator and = i is an initializer So, How can the full-expression j = i be a prvalue core constant expression?
The full-expression of the initialization constexpr const int &r = i; is the init-declarator &r = i, where &r is a declarator and = i is an initializer. So, How can the full-expression &r = i be a glvalue core constant expression?
There's obviously a wording gap in the standard. Your understanding is correct: the standard requires the "full-expression of the initialization" of a constexpr variable to be a "constant expression", and the standard also states that a "constant expression" is either a "glvalue core constant expression" or a "prvalue core constant expression" (each of which must satisfy some further requirements) so it seems that the standard is telling us that the full-expression of an initialization, which is not a true expression and therefore can never be a glvalue or a prvalue (since value categories are only applicable to expressions), is neither a "glvalue core constant expression" nor a "prvalue core constant expression" and is, therefore, not a "constant expression" at all. This interpretation is obviously not intended, because it would make it impossible to have any constexpr variables at all.
In practice, compilers seem to take the interpretation that, solely for the purposes of [expr.const]/11:
the initialization of a reference variable r is a glvalue designating r, and
the initialization of a non-reference variable o is a prvalue that stores into its result object the value of o, and
in both cases, the full-expression of the initialization includes various subexpressions that are required by the initialization, e.g., the evaluation of the initializer (if any), the constructor call that initializes the variable (if any), the conversions of initializer-clauses to aggregate element types, and any implicit calls to conversion functions. It is this full-expression that needs to be checked as a core constant expression (i.e. we have to take it as E in [expr.const]/5).
Any other interpretation would be highly counterintuitive and lead to absurdities.
Related
Consider the following code:
struct S
{
constexpr S(){};
constexpr S(const S &r) { *this = r; };
constexpr S &operator=(const S &) { return *this; };
};
int main()
{
S s1{};
constexpr S s2 = s1; // OK
}
The above program is well-formed but I'm expecting it to be ill-formed, because [expr.const]/(5.16) says:
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:
[..]
(5.16) a modification of an object ([expr.ass], [expr.post.incr], [expr.pre.incr]) unless it is applied to a non-volatile lvalue of
literal type that refers to a non-volatile object whose lifetime began
within the evaluation of E;
[..]
Given the expression E is s1. The expression E evaluates a modification of the object *this. The modification is applied to the non-volatile lvalue *this which is of literal type, and this lvalue refers to a non-volatile object which is s1 but the lifetime of s1 does not begin within the evaluation of E: That's the lifetime of the object s1 began before the evaluation of the expression E.
So I'm expecting the program is ill-formed because the "unless" part does not satisfy which means, to me, that the expression E is not a core constant expression. So what I'm missing here? Am I misreading (5.16)?
As Language Lawyer points out, there are no modifications in your program, however even if the constructor did modify s2, this wouldn't necessarily make the program ill-formed, because:
In any constexpr variable declaration, the full-expression of the initialization shall be a constant expression ([expr.const]).
-- [dcl.constexpr]/7
The constructor is allowed to modify s2 because the lifetime of s2 begins within the full-expression of the initialization, and only the full-expression of the initialization is required to be a constant expression (not the initializer in isolation).
Addendum: The only modifications that are relevant for [expr.const]/5.16 are modifications to scalar objects. This is because only modifications to scalar objects actually result in accesses to the memory of the abstract machine (see also defns.access). When I say the constructor is allowed to modify s2, I mean the constructor is allowed to modify scalar subobjects of s2.
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.
About the full-expression, there are some quotes in the standard, they are:
A full-expression is
an unevaluated operand,
a constant-expression,
an init-declarator or a mem-initializer, including the constituent expressions of the initializer,
an invocation of a destructor generated at the end of the lifetime of an object other than a temporary object, or
an expression that is not a subexpression of another expression and that is not otherwise part of a full-expression.
For an initializer, performing the initialization of the entity (including evaluating default member initializers of an aggregate) is also considered part of the full-expression.
Now, please consider the below code:
struct A{
constexpr A(int){
}
};
int main(){
constexpr A t = 0; //#1
}
A constexpr specifier used in an object declaration declares the object as const. Such an object shall have literal type and shall be initialized. In any constexpr variable declaration, the full-expression of the initialization shall be a constant expression.
Look at #1, it's a declaration, according to the standard, within this declaration, the init-declarator is a full-expression, So what's the full-expression of initialization, if it's A::A(0) (comprise the conversion), it would be contradict with the bullet point 5 of the full-expression, that's an expression that is not otherwise part of a full-expression. Obviously, the A::A(0) is part of the full-expression init-declarator, Hence A::A(0) shouldn't be a full-expression.
If the full-expression of initialization is referred to init-declarator, Due to a init-declarator is not a expression, how does a init-declarator could be a constant-expression? I'm very confused here, If my understanding is wrong, what actually the full-expression of initialization is?
The following program compiles successfully with all major compilers:
struct S {
constexpr S(const S&){};
constexpr S() = default;
};
int main(void) {
S s1{};
constexpr S s2{ s1 };
}
The rule governing the initialization of constexpr variables is [dcl.constexpr]/10: (emphasis mine)
A constexpr specifier used in an object declaration declares the object as const. Such an object shall have a literal type and shall be initialized. In any constexpr variable declaration, the full-expression of the initialization shall be a constant expression (7.7). A constexpr variable shall have constant destruction.
Per the bold part, the full-expression of the initialization shall be a constant expression. Per my understanding, the full-expression here is the init-declarator per [into.execution]/5:
A full-expression is
[..] (5.4) an init-declarator ([dcl.decl]) [..]
Per the grammar of init-declarator, the init-declarator is a declarator followed by an initializer:
init-declarator:
declarator initializer
Given this information, we can conclude that the full-expression of the initialization constexpr S s2{ s1 }; is s2{ s1 } where s2 is a declarator and { s1 } is an initializer.
Now, [dcl.constexpr]/10 tells us that the full-expression (which is s2{ s1 } init-declarator) shall be a constant expression. And I've stucked at this point. Per [expr.const]/11, a constant expression is either a glvalue or prvalue core constant expression (with some additional constraints).
For the above example, I found myself reading [dcl.constexpr]/10 as: "the full-expression s2{ s1 } shall be a glvalue or prvalue core constant expression". So how the init-declarator s2{ s1 } can be a glvalue or prvalue core constant expression? As you can see, my interpretation led me to a wrong theory. So, What did I misread/conflate here?
Intuitively, I just see the init-declarator s2{ s1 } is a copy constructor call with s1 as an argument. But that is not the problem I'm trying to understand. Also, why the above program is well-formed is not what I'm trying to ask for. Instead, I need to know why my interpretation of this rule has not brought us to a reasonable conclusion.
EDIT
Note, the same problem we will encounter with this simple example (Demo):
int main(void) {
static const int i = 42;
constexpr int j = i;
constexpr const int &r = i;
}
The full-expression of the initialization constexpr int j = i; is the init-declarator j = i, where j is a declarator and = i is an initializer So, How can the full-expression j = i be a prvalue core constant expression?
The full-expression of the initialization constexpr const int &r = i; is the init-declarator &r = i, where &r is a declarator and = i is an initializer. So, How can the full-expression &r = i be a glvalue core constant expression?
There's obviously a wording gap in the standard. Your understanding is correct: the standard requires the "full-expression of the initialization" of a constexpr variable to be a "constant expression", and the standard also states that a "constant expression" is either a "glvalue core constant expression" or a "prvalue core constant expression" (each of which must satisfy some further requirements) so it seems that the standard is telling us that the full-expression of an initialization, which is not a true expression and therefore can never be a glvalue or a prvalue (since value categories are only applicable to expressions), is neither a "glvalue core constant expression" nor a "prvalue core constant expression" and is, therefore, not a "constant expression" at all. This interpretation is obviously not intended, because it would make it impossible to have any constexpr variables at all.
In practice, compilers seem to take the interpretation that, solely for the purposes of [expr.const]/11:
the initialization of a reference variable r is a glvalue designating r, and
the initialization of a non-reference variable o is a prvalue that stores into its result object the value of o, and
in both cases, the full-expression of the initialization includes various subexpressions that are required by the initialization, e.g., the evaluation of the initializer (if any), the constructor call that initializes the variable (if any), the conversions of initializer-clauses to aggregate element types, and any implicit calls to conversion functions. It is this full-expression that needs to be checked as a core constant expression (i.e. we have to take it as E in [expr.const]/5).
Any other interpretation would be highly counterintuitive and lead to absurdities.
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.