As you may know, and as described in Chapter 4 of the Guide on this site, functions (and variables) may be declared constexpr
in Modern C++. Functions declared with this keyword are guaranteed to be able to be called at compile-time, returning a result which can be used where a compile-time constant is needed. Importantly, these functions can also be called at with run-time-defined parameters, with the result of the function calculated at run-time with the same logic.
The new keyword consteval
, added in C++20, can be used with function definitions to ensure that evaluation is performed at compile-time only. The restrictions are similar to those for constexpr
functions (global state cannot be modified) with the added restriction that only constant parameter values are passed to the function when it is called. The following function definition is a valid consteval
function:
consteval auto f(int i) {
auto n{ 0 };
for (int i{ 0 }; i != 41; ++i) {
n += 1;
}
return n + i;
}
A few things to note:
- The
consteval
keyword is written before the return type - An
auto
return type specifier is used here, this is permitted - A single parameter
i
is used here, this does not need to be declaredconst
- Local variables, such as
n
and the for-loop’si
here, can be defined and used - Looping constructs are permitted
- The return value is allowed to change depending upon parameter value(s)
Lambdas can also be declared with consteval
; the keyword is used in place of mutable
, and parentheses are required even if empty:
auto l = []()consteval{ return 42; };
Note that variables cannot be declared consteval
, this is not the purpose of this keyword, however the result of a consteval
function can be assigned to a const
, non-const
or constexpr
variable (possibly declared with auto
). (This assignment is guaranteed to not use a function call at run-time.)
New to C++23 (and not implemented by MSVC at the time of writing) is if consteval
. Unlike if
and if constexpr
, this form is not followed by a parenthesised expression, although an else
clause is permitted, as is the form if !constexpr
. The logic is straightforward, the if consteval
block is evaluated if program flow at this point is being evaluated in a compile-time context, otherwise the else
block is evaluated. (The logic is reversed for if !consteval
.) The following program (tested under Compiler Explorer) demonstrates this:
#include <iostream>
constexpr auto f(int i) {
return 41 + i;
}
constexpr int g(int i) {
if consteval {
return f(i);
}
else {
return f(i + 1);
}
}
int main() {
const auto i = g(1);
std::cout << "i = " << i << '\n';
auto j = g(1);
std::cout << "j = " << j << '\n';
}
The if consteval
appears within a constexpr
function; within a normal function if consteval
would never evaluate to true
. Only a function declared consteval
can be invoked within the if consteval
block, while such functions are not permitted within the else
block. A constexpr
function (as shown here) is valid in either or both parts. The output produced by this program is:
i = 42
j = 43
As can be observed, assigning g(1)
to a const
(or constexpr
) variable calls f(1)
, while assigning the same to a mutable variable calls f(2)
. Allowing if consteval
to call a stub function for the definition(s) needed at compile-time, while allowing a “full-fat” non-constexpr
/non-consteval
to be called at run-time, is the expected use case for this. Also, the return type of the two clauses of if consteval
is allowed to be different (where declared auto
), so this will likely find uses in generic programming.
To conclude this article, the keyword constinit
, new to C++20, allows a variable declared thread_local
or static
to have static initialization. The same would be true if constexpr
were used, however this would also imply constant destruction and const-qualification. For example, constinit
could be used with a type such as std::shared_ptr<T>
, which has a constexpr
constructor but not a constexpr
destructor.
Other possibilities of assignment to a constinit
variable are: constant literals, expressions evaluating to a constant, consteval
functions, and certain constexpr
functions (those not using paths which call other non-consteval
or non-constexpr
functions). The following example program demonstrates its use (expect it to take some time to compile):
consteval unsigned long long fib(unsigned n) {
return (n < 2) ? n : fib(n - 2) + fib(n - 1);
}
int main() {
static constinit auto fib28 = fib(28);
return fib28;
}
Most of the time, you should prefer constexpr
over constinit
, especially since variables which need to be static
or thread_local
are comparatively uncommon, and constexpr
is able to be used with these, too.