I ran across this seemingly odd behavior. A const
at top level can be used for array size declarations, but not when the const
is in a class.
Here's a compiler explorer demo for MSVC, CLANG and GCC all producing an error:
expression did not evaluate to a constant
It's not really a constant if it's not const
at the top level?
There's some argument to be made because top level constants can often be stored in read-only memory, while constants that are not at top level cannot. But is this behavior correct?
struct A {
const int i{ 3 };
};
int main()
{
const int ii{ 3 };
A a;
int j[a.i]{}; // C2131: expression did not evaluate to a constant
int k[ii]{};
}
CodePudding user response:
ii
is a compile-time constant. Its value is known at compile-time, and cannot be changed at runtime. So, ii
can be used for fixed array sizes at compile-time.
A::i
is not a compile-time constant. It is a non-static instance member. Its value is not known until runtime. After an A
object is constructed and its i
member is initialized, the value of i
cannot be changed because of the const
, but the caller can initialize i
with whatever value it wants, eg: A a{123};
, and thus different A
objects can have different i
values. So, i
cannot be used for fixed array sizes at compile-time. But, it can be used for dynamic array sizes at runtime, via new[]
, std::vector
, etc.
CodePudding user response:
Generally, you can use (meaning perform an lvalue-to-rvalue conversion on) objects with lifetime starting outside a constant expression only if they are variables marked constexpr
or their subobjects (plus some other special cases, that I don't think are important here, see [expr.const]/4 for details).
That you can use a const int
variable at all in a constant expression is already a very specific exception. Essentially const
-qualified integral and enumeration type variables are also usable in constant expressions if you could have added constexpr
to them (meaning that their initializer expression is a constant expression).
This exception is there I guess purely for historical reasons, since it had been allowed before constexpr
was introduced in C 11.
Note that all of this talks about variables and their subobjects. Non-static data members are specifically not variables and the exception doesn't apply to them. With constexpr
this is more obvious by not allowing it on the declaration of a non-static data member in the first place.
The historical rule was never extended to encompass other types that could be marked constexpr
, so e.g. const A a;
will not help although that would actually cause a
to be storable in read-only memory the same way a const int
would.
If an object is none of the cases mentioned above, then an lvalue-to-rvalue conversion on it in a constant expression is not allowed, since it is assumed that the value of the object is not determined at compile-time.
Now, in theory the compiler could still do some constant folding and determine that even other objects' values are definitively known at compile-time. But I think the intention is that whether or not an expression is a constant expression should be (reasonably) well-defined independently of the implementation and so shouldn't rely on how much analysis the compiler can do.
For example
A a;
A b(a);
is also guaranteed to result in b.i == 3
. How far do you want to require a compiler to go back or keep track of evaluations? You would need to make some definitive specification if you want to keep the behavior consistent between compilers. But there is already a simple method to indicate that you want the compiler to keep track of the values. You just have to add constexpr
:
constexpr A a;
constexpr A b(a);
Now b.i
can be used as array index (whether or not it is const
and whether or not it is initialized).
With the current rules, any compiler only needs to evaluate the value of objects at compile-time when it sees a constexpr
variable or a const
integral/enumeration type variable. For all other variables it doesn't need to keep track of values or backtrack when it sees them used in a context which requires a constant expression.
The additional effect of constexpr
implying const
on the variable makes sure that its value will also never be changed in a valid program and so the compiler doesn't need worry about updating or invalidating the value after the initial computation either. And whether or not an expression is a constant expression is (mostly) implementation-dependent.
CodePudding user response:
TL;DR
Your assumption that const
always implies compile time constant is incorrect. See examples at the end of this answer for more details on this.
Now the problem in using a.i
as the size of an array is that in standard C , the size of an array must be a compile time constant, but since i
is a non-static data member, it requires an object to be used on. In other words, we need this
, which is not available at compile-time but only at runtime, which in turn means that a.i
is not a constant expression, hence we get the mentioned error saying:
expression did not evaluate to a constant
To solve this, you can make i
be a constexpr static
data member, as shown below. This works because using a static data member doesn't require an object instance (and hence no this
pointer).
struct A {
constexpr static int i{ 3 };
};
int main()
{
const int ii{ 3 };
A a;
int j[a.i]{}; //Correct now and works in all compilers
int k[ii]{};
}
I just don't get why a regular const works in some places but not others.
Perhaps you assuming that const
implies compile time constant which is a wrong assumption. An example might help you understand this better:
int i = 10; //i is not a constant expression
const int size = i; //size is not a constant expression as the initializer is not a constant expression
//------vvvv------>error here as expected since size is not a constant expression
int arr[size]{};
On the other hand if you were to make i
const
as shown below, the program will work fine.
const int i = 10; //note the const added here so that now i is a constant expression
const int size = i; //size is a constant expression as the initializer is a constant expression
//------vvvv------>NO ERROR HERE as expected since size is a constant expression
int arr[size]{};