Bktero
Bktero

Reputation: 812

std::is_constant_evaluated() and const variables

Let's consider the following code:

#include <type_traits>

int foo(int arg) {
    if (std::is_constant_evaluated()) {
        return 1;
    } else {
        return 0;
    }
}  

int main() {
    const auto b = foo(0);
    return b;
}

It returns 0 with both gcc and clang. I would have expected it to return 1 instead.

If foo() is made constexpr, while b is kept simply const, then it does return 1.

What am I missing here? Thanks!

Upvotes: 4

Views: 545

Answers (2)

cigien
cigien

Reputation: 60218

You have to be a little careful with where and how you use is_constant_evaluated. There are 3 kinds of functions in C++, and is_constant_evaluated only makes sense in one of them.

// a strictly run-time function
int foo(int arg) 
{
    if (std::is_constant_evaluated()) // pointless: always false
        // ...
}

// a strictly compile time function
consteval int foo(int arg) 
{
    if (std::is_constant_evaluated())  // pointless: always true
        // ...
}

// both run-time and compile-time
constexpr int foo(int arg) 
{
    if (std::is_constant_evaluated())  // ok: depends on context in 
                                       // which `foo` is evaluated
        // ...
}

Another common mistake worth pointing out is that is_constant_evaluated doesn't make any sense in an if constexpr condition either:

{
    if constexpr (std::is_constant_evaluated()) // pointless: always true
                                                // regardless of whether foo 
                                                // is run-time or compile-time
}

Upvotes: 6

Barry
Barry

Reputation: 302852

std::is_constant_evaluated() returns true if and only if [meta.const.eval]:

evaluation of the call occurs within the evaluation of an expression or conversion that is manifestly constant-evaluated

This term, "manifestly constant-evaluated" (defined here), refers to contexts that have to be constant-evaluated. A call to a non-constexpr function (the nearest enclosing context here) is never constant evaluated, because it's non-constexpr, so this is straight-forwardly not "manifestly constant-evaluated."

Once we make it constexpr though, we're in this weird legacy quirk. Before C++11, which introduced constexpr, we could still do stuff like this:

template <int I> void f();

const int i = 42; // const, not constexpr
f<i>();           // ok

Basically, we have this carve out for specifically integral (and enumeration) types declared const that are initialized with a constant expression. Those still count as constant expressions.

So this:

const auto b = foo(0);

If foo(0) is an integral constant expression, then b is something that could be used as a compile time constant (and would be constant-initialized, if it were at namespace scope). So what happens here is we do a two-step parse. We first try to evaluate foo(0) as if it were a constant expression and then, if that fails, fall back to not doing that.

In this first parse, with foo(0) evaluated as a constant, is_constant_evaluated() is (by definition) true, so we get 1. This parse succeeds, so we end up with b as a compile-time constant.


For namespace-scope variables, constant-initialization is an important concept as well - to avoid the static initialization order fiasco. It leads to other gnarly examples (see P0595).

The important thing here is basically: is_constant_evaluated() should only be switched on to select a compile-time-safe algorithm vs a runtime algorithm, not to actually affect the semantics of the result.

Upvotes: 9

Related Questions