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?
Related
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.
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.
struct S {
constexpr S(int i): I(i),D(i) { } // full-expressions are initialization of I and initialization of D
private:
int I;
int D;
};
int main(){
constexpr S s1 = 1; //full-expression comprises call of S::S(int)
}
According to the definition of full-expression:
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.
The bullet 3 says s1 = 1 is a full-expression because it's an init-declarator and I(i) is a full-expression due to it's a mem-initializer and similarly for D(i). It means that initialize entity s1 can contain more than one full-expression? In this case, Which is the full-expression of the initialization in this set of full-expressions?
Of course full-expressions can be dynamically nested: consider
void f(int i) {
++i; // (useless) full-expression
}
void g() {
f(1); // full-expression
}
As such, there’s no conflict between initializing s1 being part of the init-declarator full-expression while also containing full-expressions for its mem-initializers.
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.
Say I want to refer to a member of an initializer_list that I already defined. Can I do it?
This code compiles and gives the expected: "13 55 " in both Visual Studio and gcc, I'd just like to know that it's legal:
const int foo[2] = {13, foo[0] + 42};
So what we have here is aggregate initialization covered in section 8.5.1 of the draft C++ standard and it says:
An aggregate is an array or a class [...]
and:
When an aggregate is initialized by an initializer list, as specified
in 8.5.4, the elements of the initializer list are taken as
initializers for the members of the aggregate, in increasing subscript
or member order. Each member is copy-initialized from the
corresponding initializer-clause [...]
Although it seems reasonable that side effects from initializing each member of the aggregate should be sequenced before the next, since each element in the initializer list is a full expression. The standard does not actually guarantee this we can see this from defect report 1343 which says:
The current wording does not indicate that initialization of a non-class object is a full-expression, but presumably should do so.
and also notes:
Aggregate initialization could also involve more than one full-expression, so the limitation above to “initialization of a non-class object” is not correct.
and we can see from a related std-discussion topic Richard Smith says:
[intro.execution]p10: "A full-expression is an expression that is not
a subexpression of another expression. [...] If a language construct
is defined to produce an implicit call of a function, a use of the
language construct is considered to be an expression for the purposes
of this definition."
Since a braced-init-list is not an expression, and in this case it
does not result in a function call, 5 and s.i are separate
full-expressions. Then:
[intro.execution]p14: "Every value computation and side effect
associated with a full-expression is sequenced before every value
computation and side effect associated with the next full-expression
to be evaluated."
So the only question is, is the side-effect of initializing s.i
"associated with" the evaluation of the full-expression "5"? I think
the only reasonable assumption is that it is: if 5 were initializing a
member of class type, the constructor call would obviously be part of
the full-expression by the definition in [intro.execution]p10, so it
is natural to assume that the same is true for scalar types.
However, I don't think the standard actually explicitly says this
anywhere.
So this is currently not specified by the standard and can not be relied upon, although I would be surprised if an implementation did not treat it the way you expect.
For a simple case like this something similar to this seems a better alternative:
constexpr int value = 13 ;
const int foo[2] = {value, value+42};
Changes In C++17
The proposal P0507R0: Core Issue 1343: Sequencing of non-class initialization clarifies the full-expression point brought up here but does not answer the question about whether the side-effect of initialization is included in the evaluation of the full-expression. So it does not change that this is unspecified.
The relevant changes for this question are in [intro.execution]:
A constituent expression is defined as follows:
(9.1) — The constituent expression of an expression is that expression.
(9.2) — The constituent expressions of a braced-init-list or of a (possibly parenthesized) expression-list are the
constituent expressions of the elements of the respective list.
(9.3) — The constituent expressions of a brace-or-equal-initializer of the form = initializer-clause are the
constituent expressions of the initializer-clause.
[ Example:
struct A { int x; };
struct B { int y; struct A a; };
B b = { 5, { 1+1 } };
The constituent expressions of the initializer used for the initialization of b are 5 and 1+1. —end example ]
and [intro.execution]p12:
A full-expression is
(12.1) — an unevaluated operand (Clause 8),
(12.2) — a constant-expression (8.20),
(12.3) — an init-declarator (Clause 11) or a mem-initializer (15.6.2), including the constituent expressions of the
initializer,
(12.4) — an invocation of a destructor generated at the end of the lifetime of an object other than a temporary
object (15.2), or
(12.5) — an expression that is not a subexpression of another expression and that is not otherwise part of a
full-expression.
So in this case both 13 and foo[0] + 42 are constituent expression which are part of a full-expression. This is a break from the analysis here which posited that they would each be their own full-expressions.
Changes In C++20
The Designated Initialization proposal: P0329 contains the following addition which seems to make this well defined:
Add a new paragraph to 11.6.1 [dcl.init.aggr]:
The initializations of the elements of the aggregate are evaluated in the element order. That is,
all value computations and side effects associated with a given element are sequenced before those of any element that follows it in order.
We can see this is reflected in the latest draft standard.