The following code does not compile with g++ 14.1 or clang++ 18.1:
#include <type_traits>
consteval int
plusone(int n)
{
return n+1;
}
constexpr int
maybeplusone(int n)
{
if (std::is_constant_evaluated()) {
n = plusone(n);
}
return n;
}
int
not_consteval()
{
return maybeplusone(1);
}
The compiler complains that n is not a constant expression. If I change the if (std::is_constant_evaluated())
to if consteval
, then the code compiles with clang++ but not g++. My questions:
Why isn't the code valid?
Should if consteval
be different from if (std::is_constant_evaluted())
, and if so is clang++ correct to accept the code with if consteval
?
Is there a better way to define a function like plusone
that should not accidentally be called at runtime, but without causing such errors? Something like static_assert(std::is_runtime_evaluated())
--except that's presumably useless as the static assert will always be evaluated at compile time.
For what it's worth, in my more complex application I have different memory allocators for compile time and run time, so I want to do something like this
if consteval {
p = my_consteval_allocator(n);
}
else {
p = my_real_allocator(n);
}
with similar logic for deallocation. Unfortunately, the compiler is trying to call my_consteval_allocator in non-constexpr contexts when it knows the allocation size required.
if (std::is_constant_evaluted())
is just a completely normal if
control flow, only that std::is_constant_evaluted()
evaluates to either true
or false
depending on whether or not the evaluation happens in a context that is manifestly constant-evaluated.
In particular, if a call to a consteval
function appears in an if (std::is_constant_evaluted())
branch then, usually, the call to this function must in itself be a constant expression where it appears.
Because n
is a function parameter whose value is not known at compile time plusone(n)
when the function is defined, it is not a constant expression.
if consteval
has stronger semantics. It is known that the first branch of such an if
can only ever be executed at compile time and therefore calls to consteval
functions appearing within it are not required to be themselves constant expressions. They must only be constant subexpressions when evaluating the whole constant expression that leads to the call of the function containing the if consteval
statement.
So, always use if consteval
and forget that std::is_constant_evaluted()
exists, unless you need to support C++20. But if you need that, then you can't use consteval
functions either.
In not_consteval
, the call to maybeplusone
is not a context that is manifestly constant-evaluated and therefore the runtime branch of if consteval
will be chosen. The code is valid with if consteval
instead of if(std::is_constant_evaluated())
and it compiles for me on both GCC and Clang.
Regarding your last example: With if consteval
it should be fine and the compiler ought to not call my_consteval_allocator
except in the context of an evaluation that is manifestly constant-evaluated. The items that can likely give you problems are the special cases for initialization of const
integral or enumeration type variables and for static or thread storage duration variables. In these cases constant expression evaluation can happen although it isn't obvious from the syntax.
However, it is a problem that the runtime and compile-time branches have different meaning. You can easily cause an allocation to happen in a manifestly constant-evaluated context, then leak the pointer into a runtime context and accidentally call the runtime deallocation on it. You would always have to manually verify the context in which the functions are called so that they match.
Of course such a leak shouldn't happen. An allocation in a constant expression must also always be deallocated in the same expression. So if my_consteval_allocator
uses new
, then the compiler will complain about this situation already on the allocation anyway.