I recently realized I have the following in my C++ code, and it compiles and runs without any problems.
void MyClass::foo(int a) {
const double x = a;
...
//do stuff with x
...
}
My question: I thought const variables were assigned a value at compile time and this would have given me a compile error, though in this case it obviously is assigned at runtime. Is the const specifier here being ignored? Or is there something else more complicated going on? Should I remove the const specifier?
Constant variables are assigned a value when initialized (at run-time), and cannot be modified afterwards. References and pointers to constant variables can only be used to read from those variables, the underlying variable being constant or not.
I thought const variables were assigned a value at compile time
What you are describing are C++11 constexpr variables.
Should I remove the const specifier?
No. You should make everything const unless you specifically need it to not be const.
Related
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]{};
}
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.
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.
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, after construction of the class object nonstatic data member i gets initialized, 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]{};
int a = 5;
const int b = a, c = 4;
int e[a];
int d[b];
int f[c];
The definition of f[c] is valid.
The variable b is also a constant int, but the compiler gave me the error "expression must have a constant value" for the line int d[b]. What are the differences between b and c?
what are the differences between b and c?
c has a compile time constant initialiser, while b does not. A const object with compile time constant initialiser is itself a compile time constant value.
Since I can define an lenth of an arry using a constant ,so why don't this work?
Not just any constant will do. const qualifier implies runtime constness (i.e the value may be determined at runtime but won't change throughout the lifetime of the object). Only compile time constant values can be used as array size.
You are using a non-constant variable to assign value to a constant. Therefore, that variable's value can't be determined compile time. I know you aren't changing a, but the compiler does not think like this.
The compiler diagnostic should really be compile time evaluable constant expression.
Since the original object to which b is assigned is not const, b is not a compile time evaluable constant expression, so compilation will fail as variable length arrays are not supported in standard C++.
The "const" just only means that the variable will not change at run time, and does not mean that its value can be deduced at compiling time.
I guess what you are looking for is "constexpr".
Maybe you can try it out like this:
constexpr int b = 4; // can't be assigned from a!
int d[b];
The "constexpr" instead means a "real const" and the "const" only means "non-changing var", because some historical reason.
It must be mind that a native array in c++ always is fixed-length as #Bathsheba said.
The term "constant" is really ambiguous, and the keyword const is misleading.
const means: "don't allow this object's value won't be changed after initialisation". It does not mean "compile-time constant". Its initial value can (and usually does) still come from runtime sources.
You need a compile-time constant for an array bound.
It is sometimes possible to have an object named foo that is compile-time constant, if it is const and it was initialised from a constant expression, like a literal or a constexpr thing. That's the case for c; it is not the case for b.
AFAIK removing constness from const variables is undefined behavior:
const int i = 13;
const_cast<int&>(i) = 42; //UB
std::cout << i << std::endl; //out: 13
But are const function arguments "real" constants? Let's consider following example:
void foo(const int k){
const_cast<int&>(k) = 42; //UB?
std::cout << k << std::endl;
}
int main(){
foo(13); //out: 42
}
Seems like compiler doesn't apply the same optimizations to const int k as to const int i.
Is there UB in the second example? Does const int k help compiler to optimize code or compiler just checks const correctness and nothing more?
Example
The i in const int i = 13; can be used as constant expression (as template argument or case label) and attempts to modify it are undefined behavior. It is so for backwards compatibility with pre-C++11 code that did not have constexpr.
The declarations void foo(const int k); and void foo(int k); are declaring same function; the top level const of parameters does not participate in function's signature. Parameter k must be passed by value and so can't be "real" constant. Casting its constness away is not undefined behavior. Edit: But any attempt to modify it is still undefined because it is const object [basic.type.qualifier] (1.1):
A const object is an object of type const T or a non-mutable subobject of such an object.
By [dcl.type.cv] 4 const object can't be modified:
Except that any class member declared mutable (10.1.1) can be modified, any attempt to modify a const object during its lifetime (6.8) results in undefined behavior.
And since function parameters are with automatic storage duration a new object within its storage can't be also created by [basic.life] 10:
Creating a new object within the storage that a const complete object with static, thread, or automatic storage duration occupies, or within the storage that such a const object used to occupy before its lifetime ended, results in undefined behavior.
It is unclear why k was declared const at the first place if there is plan to cast its constness away? The only purpose of it feels to be to confuse and to obfuscate.
Generally we should favor immutability everywhere since it helps people to reason. Also it may help compilers to optimize. However where we only declare immutability, but do not honor it, there it works opposite and confuses.
Other thing that we should favor are pure functions. These do not depend on or modify any external state and have no side-effects. These also are easier to reason about and to optimize both for people and for compilers. Currently such functions can be declared constexpr. However declaring the by-value parameters as const does not help any optimizations to my knowledge, even in context of constexpr functions.
But are const function arguments "real" constants?
I think the answer is yes.
They won't be stored in ROM (for example), so casting away const will at least appear to work OK. But my reading of the standard is that the the parameter's type is const int and therefore it is a const object ([basic.type.qualifier]), and so modifying it is undefined ([dcl.type.cv]).
You can confirm the parameter's type fairly easily:
static_assert( std::is_same_v<const int, decltype(k)> );
Is there UB in the second example?
Yes, I think there is.
Does const int k help compiler to optimize code or compiler just checks const correctness and nothing more?
In theory the compiler could assume that in the following example k is not changed by the call to g(const int&), because modifying k would be UB:
extern void g(const int&);
void f(const int k)
{
g(k);
return k;
}
But in practice I don't think compilers take advantage of that, instead they assume that k might be modified (compiler explorer demo).
I'm not sure what you mean by a "real" const, but here goes.
Your const int i is a variable declaration outside of any function. Since modifying that variable would cause Undefined Behaviour, the compiler gets to assume that its value will never change. One easy optimization would be that anywhere you read from i, the compiler doesn't have to go and read the value from main memory, it can emit the assembly to use the value directly.
Your const int k (or the more interesting const int & k) is a function parameter. All it promises is that this function won't be changing the value of k. That doesn't mean that it cannot be changed somewhere else. Each invocation of the function could have a different value for k.
In Primer (while studying constexpr) I found that:
variables defined inside a function
ordinarily are not stored at a fixed address. Hence, we cannot use a constexpr
pointer to point to such variables
Is it valid for all the values calculated at compile time or is it just a constraint for using keyword constexpr?
Does const keyword not ensures that the object would be determined (evaluated its value) at compile time despite being initialised by a literal?
Why define some reference with constexpr keyword:
int i=9; //Declared as global variable
constexpr int &ref=i;
since constexpr implies top-level constness which means the ref would be constant (which was true even when constexpr wasn't used as we can't refer to any other variable) and it fails to deliver the something which const reference does?
You seem to have a few misconceptions.
constexpr values are pretty much the only ones that behave like compile-time constants. So in q1 you're making a distinction that does not exist. You can store any address in a regular const pointer i.e. T* const .
Q2 is pretty much the same misconception. You can store a user's input in a const std:: string. That only means you can't change the string later.
Q3 is just a case of the language not trying to ban unnecessary things. There are a million other redundant things you can do.
I'm just wondering whether sentences like const int N=10 will be executed at compilation time. The reason I'm asking is because that the following code will work.
int main()
{
const int N=10;
int a[N]={};
return 0;
}
But this one wouldn't.
int main()
{
int N=10;
int a[N]={};
return 0;
}
The compiler must generate code "as if" the expression was evaluated at
compile time, but the const itself isn't sufficient for this. In
order to be used as the dimension of an array, for example, expression
N must be a "constant integral expression". A const int is
a constant integral expresion only if it is initialized with a constant
integral expression, and the initialization is visible to the compiler.
(Something like extern int const N;, for example, can't be used in
a constant integral expression.)
To be a constant integral expression, however, the variable must be
const; in your second example, the behavior of the compiler and the
resulting program must be "as if" the expression were only evaluated at
runtime (which means that it cannot be used as the dimension of an
array). In practice, at least with optimization, the compiler likely
would evaluate N at compile time, but it still has to pretend it
can't, and refuse to compile the code.
The compiler will probably evaluate both of the examples you provided at compile time, since even though the int N = 10; isn't const, you're not changing it anywhere and you're only assigning a constant value to it, which means the compiler can optimize this.
I recommend you take a look at the constexpr keyword introduced in C++11, which is exactly about being able to evaluate things at compile time.
Compilers will resolve const variables to literals at compile time (and also const expressions, see constant folding). The reason that the first method works is that compiler knows how much space to allocate (10*sizeof(int)) to a in the first method. In the second method the value of N is not known at compile time, and as such there is no way for the compiler to know how much space to allocate for a. Hope that helps.
This sort of thing is an implementation detail that technically is up to the compiler to choose. It could be different on different platforms.
In practice, with the most common compilers:
const int sometimes is and sometimes isn't baked at compile time. For example, the compiler clearly can't hardcode the value of a below into the object file:
int foo( int x )
{
const int a = x+ 1;
return a * 2;
}
In that function, const means it is only constant within the scope of foo(), but it is still a local stack variable.
On the other hand, const int x = 5 seems to be a literal that is usually resolved at compile time by GCC and MSVC (except sometimes they don't turn it into a literal for reasons unclear). I've seen some other compilers that won't turn it into a literal, and always put const int on the stack like an ordinary local variable.
const static int is different, because its scope is static, which means it outlives the function it is declared in, which means it will never change over the life of the program. Every compiler I've ever worked with has turned const static primitives into compile-time literals.
Objects with constructors, however, will still need to be initialized at runtime; so
class Foo {
Foo() : { CallGlobalFunction(); }
};
const static Foo g_whatever;
cannot be optimized into a literal by the compiler.