std::is_constant_evaluated()
is one the many new small features of C++20. Specified in P0595R2
, this function is available in the <type_traits>
header. Here is its description from cppreference:
constexpr bool is_constant_evaluated() noexcept;
Detects whether the function call occurs within a constant-evaluated context. Returns
true
if the evaluation of the call occurs within the evaluation of an expression or conversion that is manifestly constant-evaluated; otherwise returnsfalse
.
This sounds pretty straight-forward.
Benefits for constexpr
functions
This may be very interesting for constexpr
functions. You probably know that you have no guarantee that a constexpr
function is really executed at compile-time. This depends on how the function is called:
constexpr int foo() {
return 0;
}
constexpr auto a = foo(); // compile-time executed
auto b = foo(); // runtime executed (*)
(*) : in fact, there is no guarantee the function is evaluated at runtime. Based on the content of the function, the scope of the variable, on the optimization level, or other factors, the compiler may evaluate b
at compile-time.
Sometimes, you may want to behave differently whether the function is compile-time or runtime executed. With std::is_constant_evaluated()
, it is now possible:
constexpr int foo() {
if (std::is_constant_evaluated()) {
return 0;
} else {
std::cout << "at runtime\n";
return 1;
}
}
Based on the context, foo()
will return either 0 or 1. At mentioned above, you may be surprised. For instance, with clang 11.0.0, the following code returns 1:
int a = foo();
int main() {
int b = foo();
return a + b;
}
Indeed, a
is constant-evaluated. Apparently, it is because a
has a static storage duration (while b
has automatic storage duration) and this "is another context that might be evaluated at compile time [...] and this too interacts with std::is_constant_evaluated."
In non-constexpr
functions
It turns out that the usage of std::is_constant_evaluated()
is relevant only in constexpr
function. See this discussion on stackoverlow: "C++20 | std::is_constant_evaluated() and const variables".
For instance, it is pointless to use it in a regular function to initialize a const
variable (which is never considered as a constant-evaluated context, see "call (3)" in P0595R2), or in aconsteval
function (which is for sure called in constant-evaluated context).
Note that the proposal states:
The standard doesn't actually make a distinction between "compile time" and "run time", and hence a more careful specification is needed, one that fits the standard framework of "constant expressions".
Our approach is to precisely identify a set of expressions that are "manifestly constant-evaluated" (a new technical phrase) and specify that our new function returns true during the evaluation of such expressions and false otherwise.
This is why cppreference uses "constant-evaluated context" and not "compile-time context".
Warnings in obvious cases
According to cppreference:
When directly used as the condition of
static_assert
declaration orconstexpr if
statement, std::is_constant_evaluated()always returns
true`.
You will probably get a warning from your compiler in such cases.
With clang:
bash
warning: 'std::is_constant_evaluated' will always evaluate to 'true' in a manifestly constant-evaluated expression [-Wconstant-evaluated]
With gcc (if you add the option -Wall
):
cpp
warning: 'std::is_constant_evaluated' always evaluates to true in 'if constexpr' [-Wtautological-compare]
Conclusion
std::is_constant_evaluated()
is a great way to select a compile-time friendly algorithm in constexpr
function. Stay the safe edge of sanity and don't use it to completely change the behavior of a function as I did in my examples 😉
Top comments (2)
That's interesting! What would be an example of where you'd want a function to change depending on whether it's compile or runtime?
There is an example in the proposal to compute the power of a number.
There is a possible application case in my current project. We have a custom type for fixed-point numbers. We have a function to compute sine and cosine for this type, thanks to a lookup table of 1024 values. This is faster (to compute) but less accurate (especially if the table is small).
We may use
std::is_constant_evaluated
here: