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.
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.
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).
Recently I was surprised that the following code compiles in clang, gcc and msvc too (at least with their current versions).
struct A {
static const int value = 42;
};
constexpr int f(A a) { return a.value; }
void g() {
A a; // Intentionally non-constexpr.
constexpr int kInt = f(a);
}
My understanding was that the call to f is not constexpr because the argument i isn't, but it seems I am wrong. Is this a proper standard-supported code or some kind of compiler extension?
As mentioned in the comments, the rules for constant expressions do not generally require that every variable mentioned in the expression and whose lifetime began outside the expression evaluation is constexpr.
There is a (long) list of requirements that when not satisfied prevent an expression from being a constant expression. As long as none of them is violated, the expression is a constant expression.
The requirement that a used variable/object be constexpr is formally known as the object being usable in constant expressions (although the exact definition contains more detailed requirements and exceptions, see also linked cppreference page).
Looking at the list you can see that this property is required only in certain situations, namely only for variables/objects whose lifetime began outside the expression and if either a virtual function call is performed on it, a lvalue-to-rvalue conversion is performed on it or it is a reference variable named in the expression.
Neither of these cases apply here. There are no virtual functions involved and a is not a reference variable. Typically the lvalue-to-rvalue conversion causes the requirement to become important. An lvalue-to-rvalue conversions happens whenever you try to use the value stored in the object or one of its subobjects. However A is an empty class without any state and therefore there is no value to read. When passing a to the function, the implicit copy constructor is called to construct the parameter of f, but because the class is empty, it doesn't actually do anything. It doesn't access any state of a.
Note that, as mentioned above, the rules are stricter if you use references, e.g.
A a;
A& ar = a;
constexpr int kInt = f(ar);
will fail, because ar names a reference variable which is not usable in constant expressions. This will hopefully be fixed soon to be more consistent. (see https://github.com/cplusplus/papers/issues/973)
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.