N4527 5.20 [expr.const]p3
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.
5.20 [expr.const]p5
A constant expression is either a glvalue core constant expression whose value refers to an entity that is a permitted result of a constant expression (as defined below), or a prvalue core constant expression whose
value is an object where, for that object and its subobjects:
(5.1) — each non-static data member of reference type refers to an entity that is a permitted result of a constant
expression, and
(5.2) — if the object or subobject is of pointer type, it contains the address of an object with static storage
duration, the address past the end of such an object (5.7), the address of a function, or a null pointer
value.
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.
void foo(){
const int a = 1;//a has automatic storage duration
// all ok in gcc 5.1.0 and clang 3.8.0
int b[a]{};
static_assert(a,"");
switch(1){
case a:
;
}
}
Question1: Is a an integral constant expression?
Question2: Is a a constant expression?
Question3: Is a glvalue integral constant expression a constant expression?
Question4:
If the answer of question 3 is yes,
does this conflict with 5.20 p3 if the object has automatic storage duration?
Is a an integral constant expression?
In the following contexts:
int b[a]{};
static_assert(a,"");
switch(1){
case a:
;
}
yes, a is an integral constant expression. Starting with your first quote:
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.
'a' is an integral type, in your cases it will be implicitly converted to a prvalue, so now is a a core constant expression? Yes, if we go back to paragraph 2 which defines what is not a core constant expression:
A conditional-expression e is a core constant expression unless the evaluation of e, following the rules of the
abstract machine (1.9), would evaluate one of the following expressions
it has the following clause:
an lvalue-to-rvalue conversion (4.1) unless it is applied to
with the following exception:
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
which applies to a since it is non-volatile, const and is initialized with a constant expression.
Is a a constant expression?
In the same contexts as above, yes, since we can see from the quote above it is a core constant expression.
Is a glvalue integral constant expression a constant expression?
No, in order for it to be a integral constant expression it must be converted to a prvalue and threfore can not be a glvalue.
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.
[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.
Strictly according to the rules of C++14, at least the ones given by cppreference.com, isn't the line (1) a constant expression?
constexpr const int* addr(const int& ir) { return &ir; }
constexpr const int* tp = addr(5); // (1)
It's true that is not an address constant expression, because &ir isn't the address of a static object (&ir is the address of a temporary in this context, which cannot be known in compilation-time).
But it is a core constant expression because it doesn't violate any of the back-listed rules of core constant expression, which has no back-listed rules about getting the address of an object.
No, addr(5) is not a constant expression, and therefore the posted code is ill-formed. Yes, you are correct that addr(5) is a core constant expression.
I can see why you might think from the cppreference page alone that core constant expressions, integral constant expressions, converted constant expressions, literal constant expressions, reference constant expressions, and address constant expressions are all types of constant expression. But the true definition is given in C++14 [expr.const]/4:
A constant expression is either a glvalue core constant expression whose value refers to an object with static storage duration or to a function, or a prvalue core constant expression whose value is an object where, for that object and its subobjects:
each non-static data member of reference type refers to an object with static storage duration or to a function, and
if the object or subobject is of pointer type, it contains the address of an object with static storage duration the address past the end of such an object, the address of a function, or a null pointer value.
Being a "core constant expression" does not have many direct implications; it's merely a term used to help define "integral constant expression", "converted constant expression of type T", and "constant expression". And "constant expression" actually describes a subset of the expressions described by "core constant expression", not a superset.
For example, and to be complete, the paragraph that makes the example ill-formed ([dcl.constexpr]/9) requires a constant expression, not a core constant expression, as initializer.
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, that call shall be a constant expression. Otherwise, or if a constexpr specifier is used in a reference declaration, every full-expression that appears in its initializer shall be a constant expression. [Note: Each implicit conversion used in converting the initializer expressions and each constructor call used for the initialization is part of such a full-expression. - end note]
N4527 5.20[expr.const]p5
A constant expression is either a glvalue core constant expression whose value refers to an entity that is a
permitted result of a constant expression (as defined below), or a prvalue core constant expression whose
value is an object where, for that object and its subobjects:
— each non-static data member of reference type refers to an entity that is a permitted result of a constant expression, and
— if the object or subobject is of pointer type, it contains the address of an object with static storage duration, the address past the end of such an object (5.7), the address of a function, or a null pointer value.
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.
void foo(){
int a = 1;
int b[a || 1]{};//ok in gcc 5.1.0, error in clang 3.8.0
static_assert(a || 1,"");//ok in gcc 5.1.0, error in clang 3.8.0
switch(1){
case a || 1://ok in gcc 5.1.0, error in clang 3.8.0
;
}
}
Is a || 1 a constant expression?
N4527 5.20[expr.const]p2
A conditional-expression e is a core constant expression unless the evaluation of e, following the rules of the
abstract machine (1.9), would evaluate one of the following expressions:
(2.7) — an lvalue-to-rvalue conversion (4.1) 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, or
(2.7.2) — a non-volatile glvalue that refers to a subobject of a string literal (2.13.5), or
(2.7.3) — a non-volatile glvalue that refers to a non-volatile object defined with constexpr, or that refers
to a non-mutable sub-object of such an object, 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;
Is a || 1 a core constant expression?
a is not constant expression(see standard quote below) and therefore:
a || 1
Is not a constant expression either, although we know the expression has to evaluate to true the standard requires left to right evaluation here and I see no exceptions that would allow the compiler to skip the lvalue-to-rvalue conversion of a.
but:
const int a = 1;
Could be used in a constant expression because it fall under the exception from 5.20p2 (emphasis mine):
an lvalue-to-rvalue conversion (4.1) 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 (2.13.5), or
a non-volatile glvalue that refers to a non-volatile object defined with constexpr, or that refers
to a non-mutable sub-object 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
This rule is also why the original case is not a constant expression since none of the exception apply.
Perhaps gcc is allowing this:
int b[a || 1]{};
as a variable length array as an extension, although it should provide a warning using -pedantic. Although that would not explain the static_assert case, they could be constant folding it but I don't think the as-if rule would allow it to be considered a constant expression.
Update, possible gcc extension
From this bug report RHS of logical operators may render LHS unevaluated in constant-expression this looks like a possible gcc extension:
This compiles without incident, despite using a non-constant object in
a constant-expression:
int i;
static_assert( i || true, "" );
static_assert( ! ( i && false ), "" );
It appears to be assuming that || and && are commutative, but
short-circuiting only works in one direction.
and the final comment says:
I think this is a purposeful language extension, which could use a switch to disable. It would be nice if static_assert were always strict.
This seems like a non-conforming extension that should trigger a warning when using the -pedantic flag similar in vain to issue in Is it a conforming compiler extension to treat non-constexpr standard library functions as constexpr?.
C++11/C++14 Quote
Section 5.20 is section 5.19 in C++14 and C++11, the relevant quote from the draft C++14 standard is:
an lvalue-to-rvalue conversion (4.1) unless it is applied to
a non-volatile glvalue of integral or enumeration type that refers to a non-volatile const object with
a preceding initialization, initialized with a constant expression [ Note: a string literal (2.14.5)
corresponds to an array of such objects. —end note ], or
a non-volatile glvalue that refers to a non-volatile object defined with constexpr, or that refers
to a non-mutable sub-object 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;
and for the draft C++11 standard is:
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
a glvalue of literal type that refers to a non-volatile object defined with constexpr, or that refers
to a sub-object of such an object, or
a glvalue of literal type that refers to a non-volatile temporary object whose lifetime has not
ended, initialized with a constant expression;
Repeating your quote:
(2.7) — an lvalue-to-rvalue conversion (4.1) 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, or
a involves an lvalue-to-rvalue conversion. Since a is not a const object, that means a is not a core constant expression; therefore a || 1 is not one either.
However if your code were:
const int a = 1;
then a || 1 would be a core constant expression.
If the compiler checks the entire assignment chain, then it can determine that "a || 1" is a constant expression. However, since a is a variable, unless the compiler checks that a has not been assigned, it has no way of knowing that "a || 1" is a constant expression.