I'm now learning how to use static_assert. What I learned so far is that the argument of the static_assert has to be constant expression. For the following code, I don't know why the argument reference is not a constant expression: It is declared as const and initialized by integral constant 0:
int main()
{
const int &reference = 0;
static_assert(reference);
}
The error message from gcc states that the reference is not usable in a constant expression:
error: 'reference' is not usable in a constant expression.
Is there a standard rule that prohibits reference from being usable in constant expressions so that it is not a constant expression?
First of all, because you use static_assert(reference); instead of static_assert(!reference); the program is ill-formed whether or not the operand of static_assert is a constant expression, as the assertion will fail. The standard only requires some diagnostic for an ill-formed program. It doesn't need to describe the cause correctly. So for the following I will assume you meant static_assert(!reference);.
The initialization of reference is not a constant expression because the glvalue to which it would be initialized refers to an object which does not have static storage duration (it is a temporary bound to a reference with automatic storage duration), in violation of [expr.const]/11. (At least it seems to be the intention that a temporary lifetime-extended through a reference has the storage duration of the reference. This is not clearly specified though, see e.g. https://github.com/cplusplus/draft/issues/4840 and linked CWG issues.)
Therefore the reference is not constant-initialized (by [expr.cons]/2.2) and it is also not constexpr, so that the reference is not usable in constant expressions (by [expr.const]/4), which is however required of a reference which is referred to by an id-expression in a constant expression if its lifetime didn't start during the constant expression's evaluation (by [expr.const]/5.12). Declaring the reference constexpr only shifts the issue to the declaration failing for the same reason as above.
If you move the declaration of reference to namespace scope it will work (at least after resolution of CWG 2126 and CWG 2481).
Related
This is a follow-up question is my previous question: Why are member functions returning non-static data members not core constant expressions?
The reduced version of the example mentioned in that question is:
struct S {
const bool x = true;
constexpr bool f() { return x; }
};
int main() {
S s{};
static_assert(s.f()); // error: 's' is not a constexpr;
}
The applicable wording from the standard is N4861: [expr.const]/(5.1):
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.1) this ([expr.prim.this]), except in a constexpr function ([dcl.constexpr]) that is being evaluated as part of E;
As far as I can parse, the expression E is s.f() and it evaluates this since s.f() returns a non-static member this->x. But that falls under the "except" part: the member function s.S::f() is constexpr function that's being evaluated as part of s.f(). If I parsed correctly, I'm expecting s.f() to be constant expression and the assertion success.
However, this bullet doesn't specify a requirement that says that s has to be a constant expression. I can't understand why declaring s as constexpr compiles the program even though there's no requirement, defined in this bullet, for s to be constexpr.
I'm just applying the wording (5.1) in my example but I can't see that constexpr is required here unless I'm missing any other rule.
Because return x; performs lvalue-to-rvalue conversion, the whole kaboodle is not a core constant expression:
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:
an lvalue-to-rvalue conversion unless it is applied to
a non-volatile glvalue that refers to an object that is usable in constant expressions, or
a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of E;
lvalue-to-rvalue conversion is applied to this->S::x, which is generally forbidden, and neither of the exceptions apply to permit it.
The more relevant exception applies if x (which resolves to this->S::x) is an object that is usable in constant expressions. But it only would be if the struct S object were usable in constant expressions:
a non-mutable subobject or reference member of any of the above.
That requires it to be potentially-constant:
A variable is potentially-constant if it is constexpr or it has reference or const-qualified integral or enumeration type.
A constant-initialized potentially-constant variable is usable in constant expressions at a point P if ...
And S s{}; is not potentially-constant. So it is not usable in constant expressions, and neither are its subobjects.
To answer the title question, this is not a core constant expression, because it is the address of an object with automatic storage duration; that address may change at runtime. This is completely irrelevant for the static_assert in the question code: Being a constant pointer value is neither necessary nor sufficient for a this pointer to be "usable in constant expressions", which in turn is not sufficient for the object found through the pointer to be usable in constant expressions.
This is a follow-up question is my previous question: Why are member functions returning non-static data members not core constant expressions?
The reduced version of the example mentioned in that question is:
struct S {
const bool x = true;
constexpr bool f() { return x; }
};
int main() {
S s{};
static_assert(s.f()); // error: 's' is not a constexpr;
}
The applicable wording from the standard is N4861: [expr.const]/(5.1):
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.1) this ([expr.prim.this]), except in a constexpr function ([dcl.constexpr]) that is being evaluated as part of E;
As far as I can parse, the expression E is s.f() and it evaluates this since s.f() returns a non-static member this->x. But that falls under the "except" part: the member function s.S::f() is constexpr function that's being evaluated as part of s.f(). If I parsed correctly, I'm expecting s.f() to be constant expression and the assertion success.
However, this bullet doesn't specify a requirement that says that s has to be a constant expression. I can't understand why declaring s as constexpr compiles the program even though there's no requirement, defined in this bullet, for s to be constexpr.
I'm just applying the wording (5.1) in my example but I can't see that constexpr is required here unless I'm missing any other rule.
Because return x; performs lvalue-to-rvalue conversion, the whole kaboodle is not a core constant expression:
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:
an lvalue-to-rvalue conversion unless it is applied to
a non-volatile glvalue that refers to an object that is usable in constant expressions, or
a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of E;
lvalue-to-rvalue conversion is applied to this->S::x, which is generally forbidden, and neither of the exceptions apply to permit it.
The more relevant exception applies if x (which resolves to this->S::x) is an object that is usable in constant expressions. But it only would be if the struct S object were usable in constant expressions:
a non-mutable subobject or reference member of any of the above.
That requires it to be potentially-constant:
A variable is potentially-constant if it is constexpr or it has reference or const-qualified integral or enumeration type.
A constant-initialized potentially-constant variable is usable in constant expressions at a point P if ...
And S s{}; is not potentially-constant. So it is not usable in constant expressions, and neither are its subobjects.
To answer the title question, this is not a core constant expression, because it is the address of an object with automatic storage duration; that address may change at runtime. This is completely irrelevant for the static_assert in the question code: Being a constant pointer value is neither necessary nor sufficient for a this pointer to be "usable in constant expressions", which in turn is not sufficient for the object found through the pointer to be usable in constant expressions.
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]
Is it permitted to declare a non-const reference as constexpr? Example code:
int x = 1;
constexpr int& r = x;
This is accepted by gcc and clang (I tried several current and past versions of both, back to C++11, and all accepted it). However I think it should not be accepted because C++14 [dcl.constexpr/9] says:
if a constexpr specifier is used in a reference declaration, every full-
expression that appears in its initializer shall be a constant expression
and x is not a constant expression.
The language in the latest C++17 draft of [dcl.constexpr] changed and doesn't even mention constexpr references explicitly any more, I can't make head nor tail of what it is trying to say about them.
Assuming that x has static storage duration, the lvalue expression x is a perfectly valid constant expression.
If you use x in a context that requires a prvalue, which causes the lvalue-to-rvalue conversion to be applied to it, then the resulting prvalue expression - call it TO_RVALUE(x) - would not be a constant expression, for obvious reasons. But in the case of reference binding, there is no such conversion.
The code below doesn't compile under GCC 5.3.0 because the declaration of r is missing a constexpr specifier.
const int i = 1;
const int& r = i;
constexpr int j = r;
I believe the rejection is correct. How do I prove it using the working draft N4527?
First, since we're using a reference, [expr.const]/(2.9) must not be violated. (2.9.1) applies, though:
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
I.e. using r is fine, as long as the initializer - i - is a constant expression (this is shown below).
It's also necessary to check whether the l-t-r conversion in line 3 is legal, i.e. (2.7) must not be violated. However, (2.7.1) applies:
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
…so that's fine as well, since the (g)lvalue is r, and it refers to i - which is a non-volatile const object with a constant expression initializer (1).
We postponed showing that i is actually a constant expression, and once that's out of the way, we need to show that r is a constant expression.
[expr.const]/5 pertains to that:
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.
Since i is, in the above context, a (g)lvalue, it has to be a permitted result of a constant expression - which it is, since it has static storage duration and certainly isn't a temporary. Thus i is a constant expression.
r is, however, treated as a prvalue in line 3. Since we already established that r is a core constant expression, we solely need to check the bullet points. They're clearly met, though.
Hence the code is well-formed in namespace scope. It won't be in local scope, as i wouldn't be a permitted result of a constant expression anymore. Clang gives a comprehensive error message.