Reputation: 812
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
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
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