Last Update:
const vs constexpr vs consteval vs constinit in C++20
As of C++20, we have four keywords beginning with const. What do they all mean? Are they mostly the same? Let’s compare them in this article.
const vs constexpr
const, our good old fried from the early days of C++ (and also C), can be applied to objects to indicate immutability. This keyword can also be added to non-static member functions, so those functions can be called on const instances of a given type.
const doesn’t imply any “compile-time” evaluation. The compiler can optimize the code and do so, but in general, const objects are initialized at runtime:
// might be optimized to compile-time if compiled decides...
const int importantNum = 42;
// will be inited at runtime
std::map<std::string, double> buildMap() { /*...*/ }
const std::map<std::string, double> countryToPopulation = buildMap();
const can sometimes be used in “constant expressions”, for example:
const int count = 3;
std::array<double, count> doubles {1.1, 2.2, 3.3};
// but not double:
const double dCount = 3.3;
std::array<double, static_cast<int>(dCount)> moreDoubles {1.1, 2.2, 3.3};
// error: the value of 'dCount' is not usable in a constant expression
See at Compiler Explorer
Let’s see the full definition from cppreference:
Defines an expression that can be evaluated at compile time. Such expressions can be used as non-type template arguments, array sizes, and in other contexts that require constant expressions.
If you have a constant integral variable that is const initialized, or enumeration value, then it can be used at constant expression.
Since C++11, we have a new keyword - constexpr - which pushed further the control over variables and functions that can be used in constant expressions. Now it’s not a C++ trick or a special case, but a complete, easier-to-understand solution.
// fine now:
constexpr double dCount = 3.3;
std::array<double, static_cast<int>(dCount)> doubles2 {1.1, 2.2, 3.3};
Above, the code uses double, which is allowed in constant expressions.
We can also create and use simple structures:
#include <array>
#include <cstdlib>
struct Point {
int x { 0 };
int y { 0 };
constexpr int distX(const Point& other) const { return abs(x - other.x); }
};
int main() {
constexpr Point a { 0, 1 };
constexpr Point b { 10, 11 };
static_assert(a.distX(b) == 10);
// but also at runtime:
Point c { 100, 1 };
Point d { 10, 11 };
return c.distX(d);
}
In summary:
constcan be applied to all kinds of objects to indicate their immutabilityconstintegral type with constant initialization can be used in constant expressionconstexprobject, by definition, can be used in constant expressionsconstexprcan be applied to functions to show that they can be called to produce constant expressions (they can also be called at runtime)constcan be applied to member functions to indicate that a function doesn’t change data members (unless mutable),
constexpr vs consteval
Fast forward to C++20, we have another keyword: consteval. This time it can be applied only to functions and forces all calls to happen at compile time.
For example:
consteval int sum(int a, int b) {
return a + b;
}
constexpr int sum_c(int a, int b) {
return a + b;
}
int main() {
constexpr auto c = sum(100, 100);
static_assert(c == 200);
constexpr auto val = 10;
static_assert(sum(val, val) == 2*val);
int a = 10;
int b = sum_c(a, 10); // fine with constexpr function
// int d = sum(a, 10); // error! the value of 'a' is
// not usable in a constant expression
}
See @Compiler Explorer.
Immediate functions can be seen as an alternative to function-style macros. They might not be visible in the debugger (inlined)
Additionally, while we can declare a constexpr variable, there’s no option to declare a consteval variable.
// consteval int some_important_constant = 42; // error
In summary:
constevalcan only be applied to functionsconstexprcan be applied to functions and also variablesconstevalforces compile time function evaluation; theconstexprfunction can be executed at compile time but also at runtime (as a regular function).
This article started as a preview for Patrons, sometimes even months before the publication. If you want to get extra content, previews, free ebooks and access to our Discord server, join the C++ Stories Premium membership or see more information.
constexpr vs constinit
And now the last keyword: constinit.
constinit forces constant initialization of static or thread-local variables. It can help to limit static order initialization fiasco by using precompiled values and well-defined order rather than dynamic initialization and linking order…
#include <array>
// init at compile time
constexpr int compute(int v) { return v*v*v; }
constinit int global = compute(10);
// won't work:
// constinit int another = global;
int main() {
// but allow to change later...
global = 100;
// global is not constant!
// std::array<int, global> arr;
}
See at Compiler Explorer
Contrary to const or constexpr, it doesn’t mean that the object is immutable. What’s more constinit variable cannot be used in constant expressions! That’s why you cannot init another with global or use global as an array size.
In summary:
constexprvariables are constant and usable in constant expressionsconstinitvariables are not constant and cannot be used in constant expressionsconstexprcan be applied on local automatic variables; this is not possible withconstinit, which can only work on static orthread_localobjects- you can have a
constexprfunction, but it’s not possible to have aconstinitfunction declaration
Mixing
Can we have mixes of those keywords?
// possible and compiles... but why not use constexpr?
constinit const int i = 0;
// doesn't compile:
constexpr constinit int i = 0;
// compiles:
const constexpr int i = 0;
And we can have const and constexpr member functions:
struct Point {
int x { 0 };
int y { 0 };
constexpr int distX(const Point& other) const { return abs(x - other.x); }
constexpr void mul(int v) { x *= v; y *= v; }
};
In the above scenario, constexpr means that the function can be evaluated for constant expressions, but const implies that the function won’t change its data members. There’s no issue in having only a constexpr function like mul:
constexpr int test(int start) {
Point p { start, start };
p.mul(10); // changes p!
return p.x;
}
int main() {
static_assert(test(1) == 10);
}
Run at Compiler Explorer
Summary
After seeing some examples, we can build the following summary table:
| keyword | on auto variables | to static/thread_local variables | to functions | constant expressions |
|---|---|---|---|---|
const |
yes | yes | as const member functions |
sometimes |
constexpr |
yes or implicit (in constexpr functions) |
yes | to indicate constexpr functions |
yes |
consteval |
no | no | to indicate consteval functions |
yes (as a result of a function call) |
constinit |
no | to force constant initialization | no | no, a constinit variable is not a constexpr variable |
You can also find another cool article from Marius Bancila where he also compared those keywords (back in 2019): Let there be constants!.
As of C++23 there’s no new const keyword added, so the list I showed in this article should be valid for a couple of years at least :)
Back to you
- Do you use
constexpr? - Have you tried
constinit?
Share your feedback in comments below.
I've prepared a valuable bonus for you!
Learn all major features of recent C++ Standards on my Reference Cards!
Check it out here:
