23

As far as I know, the const qualifier in C++ basically declares internal linkage, and sometimes it allows the variable to be used as a constant expression so that it can be placed into array bounds, switch cases, etc.

But apparently this is not always the case, and I have no clue regarding the proper usage of const and constexpr.

Specifically, I found that when an element of a const-qualified array is used in array bounds it is not treated as a constant expression, as seen in the following code.

const int N = 3;
int foo[N] = {1, 2, 3};  // Valid

const int bar[5] = {1, 2, 3, 4, 5};
int arr[bar[2]] = {1, 2, 3};  // Invalid because a VLA can't be initialized

Using constexpr instead of const in the later part solves the problem. But why is the last statement invalid? And exactly what is required for an expression to be constant?

morimn
  • 513
  • 3
  • 10
  • That is the subtle difference between const and constexpr. Only the second one is for the compiler and tells it that this is not only a constant, but it can be computed at *compile* time and can therefore be used like a #define or a literal constant. The simple const can be computed at *runtime* and is only a promise that afterwards that value will not change again. – U. W. Jan 04 '21 at 07:53
  • 2
    But for historical reasons `const` integer values are allowed as array bounds. – john Jan 04 '21 at 07:54
  • 2
    @john: Not really - `const int N = rand()` is valid, but can't be used as an array bound. Array bounds must be `constexpr` values. However, it's acceptable for historical reasons to use a `const` integer variable when that's been initialized with such a `constexpr` value. – MSalters Jan 04 '21 at 13:54

3 Answers3

26

The only time const means the same thing as constexpr in the declaration of a variable, is when the variable is of integral or enumeration type. In addition, the initializer of this variable declaration must be a constant expression. e.g.

const int n = 42;       // same as constexpr
                        // type is int
                        // initializer is integer literal, which is constant expression

std::cin >> x;          // some user input
const int n = x;        // NOT constexpr
                        // because initializer is not a constant expression

const double n = 4.2;   // NOT constexpr
                        // type is not integral or enumeration type

Your last line of code fails to compile because bar is not an integral or enumeration type, and hence it's not a constexpr. Since it's not constexpr none of its elements are constexpr either, and hence they cannot be used as an array bound.

The reason for this special case with integers is historical: array bounds need to be constant expressions, but before C++11, the only way to express that was with a const int. Technically, the rules could be changed to require the declaration to have constexpr but that would break exisiting code and so it won't be changed.


And exactly what is required for an expression to be constant?

This is interesting, because the language doesn't actually say what is required for an expression to be a constant expression. Instead, it assumes that all expressions are constant expressions, and provides a list of conditions that if not satisfied will make the expression not a constant expression.

The rule is here:

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: ...

and this is followed by a list of conditions that make an expression not a constant expression.

cigien
  • 57,834
  • 11
  • 73
  • 112
  • How does the standard formalize it? – Evg Jan 04 '21 at 08:13
  • 1
    @Evg It's slightly indirect, but [expr.const#3](https://eel.is/c++draft/expr.const#3) and [expr.const#15.7](https://eel.is/c++draft/expr.const#15.7) together cover the rule formally. – cigien Jan 04 '21 at 08:18
  • Thanks for the clear explanation. I've always imagined an integer array element is identically of an integer type, but it was incorrect in this sense? – morimn Jan 04 '21 at 08:35
  • 3
    @morimn An element of an `int` array is definitely an `int` type, you're right about that. It's just that the rules about what makes a `const` variable a `constexpr` variable is very strict, and it doesn't apply for `int[]` even though it applies for `int`. – cigien Jan 04 '21 at 08:40
  • 1
    @morimn: The "const int only" has been in C++ since the first standard from 1998. – MSalters Jan 04 '21 at 13:55
  • 1
    Perhaps worth mentioning that in C, even `const int n = 42;` is not usable as a constant expression (e.g. an array size of a struct member, where a VLA isn't allowed even in C99). C "deals with it" by making you use the preprocessor. Making that minimal change was necessary for C++ templates like `std::array` to be usable, and C++ has had that since the beginning of C++, @morimn. – Peter Cordes Jan 04 '21 at 23:13
3

With const declaration const int bar[5] = {1, 2, 3, 4, 5}; bar[2] is treated as a variable rather than a constant.

With constexpr declaration constexpr int bar[5] = {1, 2, 3, 4, 5}; bar[2] is treated as a constant as expected.

In contrary, for pure integral types both const and constexpr declarations are threated as constants.

It's due to language rules.

For example if you look at generated assembly code for const int bar[5] = {1, 2, 3, 4, 5}; and constexpr int bar[5] = {1, 2, 3, 4, 5};, one can see that they are the same. So technically, both sould work.

So this verifies that limitations come from language rules, which have some historical reasons, as stated in some other answers.

StPiere
  • 4,113
  • 15
  • 24
0

It's perfectly in align with the standards

Using constexpr instead of const in the later part solves the problem.

Because constexpr evaluates the value at compile time

But why is the last statement invalid?

const int bar[5] = {1, 2, 3, 4, 5}; 

says that the values in bar are const values evaluated at run time


int arr[bar[2]] = {1, 2, 3};  

here you are making this check at compile time . so const is throwing error. Using constexpr solves it as it deduces it at the compile time.

And exactly what is required for an expression to be constant?

declaring const to an expression tells the compiler to treat that value as const and to not allow the programmer to modify it . both const and constexpr serves the purpose but declaring makes compiler to defer initialization until run time . As compiler is checking for const value in int arr[bar[2]] at compile time it's throwing an error.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
User
  • 572
  • 2
  • 10
  • 3
    I didn’t downvote, but it seems like this answer doesn’t give any information the OP doesn’t already know (i.e. that *sometimes* `const` is sufficient to make a value known at compile time and sometimes it isn’t). Read the other answer for the key insight, namely that *integral* values are treated specially. – Sneftel Jan 04 '21 at 08:33
  • 3
    Not my vote either, but no sane compiler will evaluate `const int bar[5] = {1, 2, 3, 4, 5};` at run time. Sure, it's allowed, but you currently suggest that's mandatory. Most compilers _do_ know at compile time that `bar[2]` is 3, and will further optimize using that. But they still need to give at least a warning when you use `bar[2]` as if it's a constant. – MSalters Jan 04 '21 at 13:59
  • Thanks @MSalters , It's really changing my outlook too .. I am loving stackoverflow – User Jan 04 '21 at 14:23
  • 1
    Of course, normal compilers will still choose to stop you from using `bar[2]` in a context where ISO C++ *requires* a constant expression, like a template param or an array size. (e.g. because in un-optimized (debug) builds like gcc/clang -O0, they choose *not* to optimize between statements. so might not do constant propagation for `bar[2]`. e.g. clang actually loads from memory for `return bar[2]`: https://godbolt.org/z/oYcvxb. Although even `constexpr` doesn't change that because it's not *forced* to eval at compile time.) – Peter Cordes Jan 04 '21 at 23:18
  • 1
    Anyway, nobody wants code that can *only* be compiled with optimization enabled, e.g. to make something visibly constant after inlining, so the sensible thing is to enforce ISO C++ const / constexpr rules for things that *require* constant expressions. But of course do as much constant-propagation as possible when you compile normally (with `-O2` or `-O3`) – Peter Cordes Jan 04 '21 at 23:20