Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to understand the sentence "full-expression must be a constant expression" which mentioned in the standard

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?

like image 420
mada Avatar asked Nov 16 '22 10:11

mada


1 Answers

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.

like image 99
Brian Bi Avatar answered Dec 14 '22 23:12

Brian Bi