DEV Community

Pierre Gradot
Pierre Gradot

Posted on • Updated on

Let's try C++20 | std::is_constant_evaluated()

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 returns false.

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 (*)
Enter fullscreen mode Exit fullscreen mode

(*) : 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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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 or constexpr if statement, std::is_constant_evaluated()always returnstrue`.

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)

Collapse
 
dynamicsquid profile image
DynamicSquid

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?

Collapse
 
pgradot profile image
Pierre Gradot • Edited

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:

  • at compile-time, we can use an accurate but slow algorithm (which we don't have yet)
  • at runtime-time, we can keep using our current algorithm