__builtin_is_constant_evaluated
is the builtin used to for implementing std::is_constant_evaluated
in the standard library on clang and gcc.
code that is not valid in constant context is also often harder for the optimizer to constant fold.
for example:
int f(int i) {
if (__builtin_is_constant_evaluated())
return 1;
else {
int* ptr = new int(1);
int i = *ptr;
delete ptr;
return i;
}
}
is emitted by gcc -O3
as:
f(int):
sub rsp, 8
mov edi, 4
call operator new(unsigned long)
mov esi, 4
mov rdi, rax
call operator delete(void*, unsigned long)
mov eax, 1
add rsp, 8
ret
so the optimizer used __builtin_is_constant_evaluated() == 0
clang fold this to a constant but this is because clang's optimizer can remove unneeded dynamic allocation, not because it used __builtin_is_constant_evaluated() == 1
.
i am aware that this would make the return value of __builtin_is_constant_evaluated()
implementation defined because optimization vary from one compiler to an other. but is_constant_evaluated should already be used only when both path have the same observable behaviors.
why does the optimizer not use __builtin_is_constant_evaluated() == 1
and fallback to __builtin_is_constant_evaluated() == 0
if it wasn't able to fold ?
Per [meta.const.eval]:
constexpr bool is_constant_evaluated() noexcept;
Returns:
true
if and only if evaluation of the call occurs within the evaluation of an expression or conversion that is manifestly constant-evaluated ([expr.const]).
f
can never be invoked in a constant-evaluated expression or conversion, so std::is_constant_evaluated()
returns false
. This is decided by the compiler and has nothing to do with the optimizer.
Of course, if the optimizer can prove that the branches are equivalent, it can do constant fold. But that is optimization after all — beyond the scope of the C++ language itself.
But why is it this way? The proposal that introduced std::is_constant_evaluated
is P0595. It explains the idea well:
constexpr double power(double b, int x) { if (std::is_constant_evaluated() && x >= 0) { // A constant-evaluation context: Use a // constexpr-friendly algorithm. double r = 1.0, p = b; unsigned u = (unsigned)x; while (u != 0) { if (u & 1) r *= p; u /= 2; p *= p; } return r; } else { // Let the code generator figure it out. return std::pow(b, (double)x); } } // ... double thousand() { return power(10.0, 3); // (3) }
[...]
Call (3) is a core constant expression, but an implementation is not required to evaluate it at compile time. We therefore specify that it causes
std::is_constant_evaluated()
to producefalse
. It's tempting to leave it unspecified whethertrue
orfalse
is produced in that case, but that raises significant semantic concerns: The answer could then become inconsistent across various stages of the compilation. For example:int *p, *invalid; constexpr bool is_valid() { return std::is_constant_evaluated() ? true : p != invalid; } constexpr int get() { return is_valid() ? *p : abort(); }
This example tries to count on the fact that constexpr evaluation detects undefined behavior to avoid the non-constexpr-friendly call to
abort()
at compile time. However, ifstd::is_constant_evaluated()
can returntrue
, we now end up with a situation where an important run-time check is by-passed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With