Understanding C++23 if consteval
if consteval
const syntax timeline:
constoriginal languageconstexprC++11if constexprC++17constevalandconstinitC++20if constevalC++23 aka "consteval if", colloquially
Background & Motivation
consteval is an "immediate" function -- every call to it has to be executed during compile time. The constexpr keyword indicates the possibility of a function being run at compile-time. If the arguments to a constexpr function were all compile-time constants, the compiler could evaluate the constexpr function at compile-time, but that would only be a compiler optimization (not required by the language, except in manifestly constant-evaluated contexts that demand a constant expression (e.g. array bound). Here the compiler is required to perform evaluation at compile time).
So pre C++23, calling constexpr from consteval does work, but calling consteval functions from constexpr functions does not work. After all, constexpr could be evaluated at run time.
Using
std::is_constant_evaluated()also won't work. And we don't expect it to! We usestd::is_constant_evaluated()in aconstexprfunction to select between a compile-time-friendly implementation and a potentially-faster runtime implementation. Function parameters are notconstexpr, and usingstd::is_constant_evaluated()within aconstexprfunction doesn't change the overall potential for runtime function execution. The compiler must still be able to generate a valid runtime version of the entire function with all code paths, so this may cause static analysis errors / fail to compile. These failures prevent the possibility of aconstevalcall ending up in runtime code.
- all code examples are currently directly from Sandor Dargo's blog! See Sources.
consteval int bar(int i) {
return 2*i;
}
constexpr int foo(int i) {
if (std::is_constant_evaluated()) {
return bar(i);
}
return 2*i;
}
int main() {
[[maybe_unused]] auto a = foo(5);
}
/*
main.cpp: In function 'constexpr int foo(int)':
main.cpp:6:14: error: 'is_constant_evaluated' is not a member of 'std'
6 | if (std::is_constant_evaluated()) {
| ^~~~~~~~~~~~~~~~~~~~~
main.cpp:7:19: error: 'i' is not a constant expression
7 | return bar(i);
| ~~~^~~
*/
Introducing C++23 if consteval!
Enter if consteval in C++23! Now we can call consteval functions from constexpr ones. The syntax looks exactly as we had wanted if (std::is_constant_evaluated) to work, but we can invoke consteval functions if the if condition evaluates to true, aka when the context is constant-evaluated, but not necessarily explicitly declared so. We continue to safely prevent consteval calls ending up in runtime code! (Also, you don't need any header include)
consteval int bar(int i) {
return 2*i;
}
int foo(int i) {
if consteval {
return bar(i);
}
return 2*i;
}
int main() {
[[maybe_unused]] auto a = foo(5);
}
How implemented
#TODO
Takeaways
The benefits from unlocking "call-consteval-from-constexpr" are in line with all motivations for expanding compile-time capabilities:
- We can implement different optimizations for compile time vs. runtime.
if constevalallows the compiler to choose between a slow but simple-to-implementconstexprcompile-time version, or a highly optimized (maybe assembly-level) runtime version. - We can access compile-time library features (e.g.
std::source_location::current()) when available during constant evaluation, while providing a fallback (or simply doing nothing) at runtime. - We can use
if constevalto wrap compile-time assertions (static_assert) or other static analysis code that should only be checked during compilation. - In certain scenarios, we can generate and populate data structures or lookup tables entirely at compile time, avoiding runtime overhead.
We should point out the difference between C++23 if consteval and C++17 if constexpr. if constexpr is a compile-time conditional that causes the compiler to discard branches of code at compilation based on a constexpr. This differs from a regular if statement, where both branches are compiled, and the condition is evaluated at runtime. if constexpr is primarily used in generic template programming and largely supplants SFINAE/manual template specialization, where different code paths may be required based on the properties of a template type (e.g., using type traits like std::is_integral_v). And in fact C++26 reflection in concert with if constexpr vastly expands our compile-time and metaprogramming toolboxes.
Sources (some)
https://www.sandordargo.com/blog/2022/06/01/cpp23-if-consteval