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:
const
can be applied to all kinds of objects to indicate their immutabilityconst
integral type with constant initialization can be used in constant expressionconstexpr
object, by definition, can be used in constant expressionsconstexpr
can be applied to functions to show that they can be called to produce constant expressions (they can also be called at runtime)const
can 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:
consteval
can only be applied to functionsconstexpr
can be applied to functions and also variablesconsteval
forces compile time function evaluation; theconstexpr
function 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:
constexpr
variables are constant and usable in constant expressionsconstinit
variables are not constant and cannot be used in constant expressionsconstexpr
can be applied on local automatic variables; this is not possible withconstinit
, which can only work on static orthread_local
objects- you can have a
constexpr
function, but it’s not possible to have aconstinit
function 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: