I am trying to initialize a constexpr reference with no success. I tried
#include <iostream>
constexpr int& f(int& x) // can define functions returning constexpr references
{
return x;
}
int main()
{
constexpr int x{20};
constexpr const int& z = x; // error here
}
but I'm getting a compile time error
error: constexpr variable 'z' must be initialized by a constant expression
Dropping the const results in
error: binding of reference to type 'int' to a value of type 'const int' drops qualifiers
even though I had the feeling that constexpr automatically implies const for variable declarations.
So my questions are:
Are constexpr references ever useful? (i.e., "better" than const references)
If yes, how can I effectively define them?
PS: I've seen a couple of questions related to mine, such as Which values can be assigned to a `constexpr` reference? , but I don't think they address my questions.
Are constexpr references ever useful? (i.e., "better" than const references)
They are guaranteed to be initiailized before the program starts, whereas a reference to const can be initialized during dynamic initialization, after the program starts running.
If yes, how can I effectively define them?
A constexpr reference has to bind to a global, not a local variable (or more formally, it has to bind to something with static storage duration).
A reference is conceptually equivalent to taking the address of the variable, and the address of a local variable is not a constant (even in main which can only be called once and so its local variables are only initialized once).
So the problem is that a constexpr reference needs to bind to an object with static storage duration, which is covered in the draft C++11 standard: N3337 section 5.19 [expr.const] (emphasis mine):
A reference constant expression is an lvalue
core constant expression that designates an object with static storage duration or a function
The draft C++14 standard: N3936 changes the wording:
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 (5.7), the address of a function, or a null pointer
value.
So changing the declaration of x like so would work:
constexpr static int x{20};
Like T.C. says, the initializer needs to be an object with static storage duration.
N4140/§5.19/4 A constant expression is either a glvalue core
constant expression whose value refers to an object with static
storage duration [...]
N4140/§7.1.5/9 A constexpr specifier used in an object declaration
declares the object as const. Such an object shall have literal type
and shall be initialized. [...] 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.
In N3337, the wording is different.
Related
I was trying to figure out the restrictions of constexpr in cpp11/14. There are some usage requirements I found in CPP14-5.19-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:
...
if the object or subobject is of pointer type, it contains the address of another 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.
I've run some tests(code shown below) for expressions that involves address-of operator &, in order to ensure the correctness of the standards' statements quoted above.
Simply put, I tried to take the address of a global int variable global_var, which is an object with static storage duration(if I was not thinking wrong), everything works just as standards points out. But, what confused me is that, when I tried to assign another pointer-type object(global_var_addr1 in code), which stored the address of the same object global_var, the program won't compile. And GCC says:
error: the value of ‘global_var_addr1’ is not usable in a constant expression
note: ‘global_var_addr1’ was not declared ‘constexpr’
, while Clang-Tidy says:
error: constexpr variable 'x2' must be initialized by a constant expression [clang-diagnostic-error]
note: read of non-constexpr variable 'global_var_addr1' is not allowed in a constant expression
and I don't know why, is there anything I missed?
So my question is:
1. Why, in a constant expression, I cannot use a pointer-type object which contains the address of an object with static storage duration, as standards says?
2. Why everything goes different in the same context as (1), when the object is auto specified?
Any advices would be welcomed, thanks in advance!
Code:
const int global_var_c = 123;
int global_var = 123;
const void *global_var_addr1 = &global_var;
const void *global_var_addr2 = nullptr;
auto global_var_addr3 = nullptr;
auto main() -> int
{
constexpr const int x00 = global_var_c; // OK
constexpr const void *x0 = &global_var; // OK
// Operate on the object of pointer type
constexpr const void *x1 = &global_var_addr1; // OK
constexpr const void *x2 = global_var_addr1; // ERROR: read of non-constexpr variable 'global_var_addr1'...
// Operate on nullptr
constexpr const void *x3 = &global_var_addr2; // OK
constexpr const void *x4 = global_var_addr2; // ERROR: read of non-constexpr variable 'global_var_addr2'...
// Operate on nullptr (with type deduction)
constexpr const void *x5 = global_var_addr3; // OK
constexpr const void *x6 = &global_var_addr3; // OK
}
In both
constexpr const void *x2 = global_var_addr1;
and
constexpr const void *x4 = global_var_addr2;
a lvalue-to-rvalue conversion happens from the variable global_var_addr1/global_var_addr2 glvalue to the pointer value they hold. Such a conversion is only allowed if the variable's lifetime began during the evaluation of the constant expression (not the case here) or if it is usable in constant expressions, meaning that it is constexpr (not the case here) or initialized by a constant expression (is the case here) and of reference or const-qualified integral/enumeration type (not the case here).
Therefore the initializers are not constant expressions.
This is different in the case of
constexpr const int x00 = global_var_c;
since global_var_c is of const-qualified integral type.
I am not exactly sure about
constexpr const void *x5 = global_var_addr3; // OK
Intuitively it should work, because the type of nullptr and consequently the deduced type of global_var_addr3 is std::nullptr_t which doesn't need to carry any state, so that a lvalue-to-rvalue conversion wouldn't be necessary. Whether the standard actually guarantees that, I am not sure at the moment.
Reading the current wording (post-C++20 draft), [conv.ptr] specifies only conversion of a null pointer constant (i.e. a prvalue of std::nullptr_t) to another pointer type and [conv.lval] specifically states how the lvalue-to-rvalue conversion of std::nullptr_t produces a null pointer constant. [conv.lval] also clarifies in a note that this conversion doesn't access memory, but I don't think that makes it not a lvalue-to-rvalue conversion given that it still written under that heading.
So it seems to me that strictly reading the standard
constexpr const void *x5 = global_var_addr3; // OK
should be ill-formed (whether global_var_addr3 is const-qualified or not).
Here is an open clang bug for this. There seems to be a link to come internal discussion by the standards committee, which I cannot access.
In any case, the auto placeholder doesn't matter. You could have written std::nullptr_t for it instead directly.
All of these are requirements for being a core constant expression, which is a prerequisite to the requirements you mention in your question.
The variable declared here is clearly not constexpr (nor even const):
const void *global_var_addr1 = &global_var;
And you can't use non-constexpr values to initialize constexpr values. So it's no surprise this fails to compile:
constexpr const void *x2 = global_var_addr1; // ERROR: read of non-constexpr
The address of a non-constexpr value can be used in cases like you've shown, however, but the value stored in a variable and the address of a variable are not the same thing.
I am trying to initialize a constexpr reference with no success. I tried
#include <iostream>
constexpr int& f(int& x) // can define functions returning constexpr references
{
return x;
}
int main()
{
constexpr int x{20};
constexpr const int& z = x; // error here
}
but I'm getting a compile time error
error: constexpr variable 'z' must be initialized by a constant expression
Dropping the const results in
error: binding of reference to type 'int' to a value of type 'const int' drops qualifiers
even though I had the feeling that constexpr automatically implies const for variable declarations.
So my questions are:
Are constexpr references ever useful? (i.e., "better" than const references)
If yes, how can I effectively define them?
PS: I've seen a couple of questions related to mine, such as Which values can be assigned to a `constexpr` reference? , but I don't think they address my questions.
Are constexpr references ever useful? (i.e., "better" than const references)
They are guaranteed to be initiailized before the program starts, whereas a reference to const can be initialized during dynamic initialization, after the program starts running.
If yes, how can I effectively define them?
A constexpr reference has to bind to a global, not a local variable (or more formally, it has to bind to something with static storage duration).
A reference is conceptually equivalent to taking the address of the variable, and the address of a local variable is not a constant (even in main which can only be called once and so its local variables are only initialized once).
So the problem is that a constexpr reference needs to bind to an object with static storage duration, which is covered in the draft C++11 standard: N3337 section 5.19 [expr.const] (emphasis mine):
A reference constant expression is an lvalue
core constant expression that designates an object with static storage duration or a function
The draft C++14 standard: N3936 changes the wording:
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 (5.7), the address of a function, or a null pointer
value.
So changing the declaration of x like so would work:
constexpr static int x{20};
Like T.C. says, the initializer needs to be an object with static storage duration.
N4140/§5.19/4 A constant expression is either a glvalue core
constant expression whose value refers to an object with static
storage duration [...]
N4140/§7.1.5/9 A constexpr specifier used in an object declaration
declares the object as const. Such an object shall have literal type
and shall be initialized. [...] 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.
In N3337, the wording is different.
I have the following sample code
template<class T1, class T2>
class Operation
{
public:
constexpr Operation(const T1& lhs, const T2& rhs) noexcept
: m_lhs(lhs), m_rhs(rhs) { }
private:
const T1& m_lhs;
const T2& m_rhs;
};
int main()
{
constexpr int a = 3;
constexpr int b = 4;
constexpr Operation op(a, b);
return 0;
}
Compiling this with cygwin (gcc 8.2) I get
error: 'Operation<int, int>{a, b}' is not a constant expression:
constexpr Operation op(a, b);
With MSVC 2019 it compiles fine, but IntelliSense ironically underlines the a in op(a, b) with the tooltip "expression must have a constant value".
Any advice as to what the issue is, and how to fix it?
Yeah, this rule is one of the more complex ones as far as constant evaluation is concerned.
Basically, you cannot have a constexpr reference to an object that doesn't have static storage duration. Taking a reference to an object is basically copying its address - and in order for an object's address to be a constant expression, the address itself needs to be constant - so it has to persist. That is, it needs to be static.
So if you change the things you're referring to to have static storage duration instead, everything works:
static constexpr int a = 3;
static constexpr int b = 4;
constexpr Operation op(a, b); // now ok
The specific rule your program violates is [expr.const]/10, and T.C. helped me understand how it applies. Declaring a constexpr variable requires the initialization to be a constant expression ([dcl.constexpr/10]):
In any constexpr variable declaration, the full-expression of the initialization shall be a constant expression.
We don't say this, but it makes sense and certainly helps resolve this particular situation, but the "full-expression of the initialization" can be interpreted as a prvalue -- since a prvalue is an expression whose evaluation initializes an object ([basic.lval]/1).
Now, [expr.const]/10 reads:
A constant expression is either a glvalue core constant expression [...], or or a prvalue core constant expression whose value satisfies the following constraints:
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,
[...],
if the value is an object of class or array type, each subobject satisfies these constraints for the value.
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.
The initialization Operation(a, b) is a prvalue, so we need each reference data member to refer to an entity that is permitted as a result of a constant expression. Our reference data members refer to a and b, neither of which has static storage duration nor are temporaries nor are non-immediate functions. Hence, the overall initialization isn't a constant expression, and is ill-formed.
Making a and b static gives them static storage duration, which makes them permitted results of constant expressions, which makes the prvalue initialization satisfy all the requirements, which makes the declaration of op valid.
This is all a long winded way of saying: when dealing with constant evaluation, everything everywhere has to be constant all the way down. Some of our ways of wording this are very complex (like this one), but it's based on the fundamental idea that the model of constant evaluation is basically like pausing evaluating the code to go run a separate program to produce an answer. Producing op requires these addresses to be known, fixed things - and that only happens for static storage duration.
Here, at http://eel.is/c++draft/expr.const#2.7 it can be read
3 -
A variable is usable in constant expressions after its initializing declaration is encountered if it is a constexpr variable, or it is of
reference type or of const-qualified integral or enumeration type, and
its initializer is a constant initializer.
Well, the case is that I cannot figure myself an example for the case where the variable is of reference type.
In
int main()
{
static const double& ds = 0.5;
constexpr double cds = ds;
}
Visual Studio 16.1.2 complains:
expression must have a constant value.
the value of variable (declared at line 11) cannot be used as a constant
But, why?
Your code is not legal in C++17 because it contains a constexpr variable whose initialization requests lvalue-to-rvalue conversion and it is not one of the listed exceptions: (C+17 [expr.const]/2.7)
an lvalue-to-rvalue conversion (7.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, or
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
a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of e;
You should find the code compiles if you change to an integer type instead of double.
The text you quote is from a draft and as such, may or may not eventually form part of some standard.
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.