I'm observing an inconsistency between GCC and Clang with respect to what is a constant evaluated context. I played with different situations:
#include <iostream>
#include <type_traits>
consteval auto ceval(auto x) { return x * x; }
constexpr auto constexprif(auto x) {
if constexpr (std::is_integral_v<decltype(x)>) {
return ceval(x);
} else {
return x + 2.;
}
}
constexpr auto constexprif_consteval(auto x) {
if constexpr (std::is_integral_v<decltype(x)>) {
if consteval {
return ceval(x);
} else {
return x * x + 1;
}
} else {
return x + 2.;
}
}
constexpr auto constevalif(auto x) {
if consteval {
return ceval(x);
} else {
return static_cast<decltype(x)>(x + 2.);
}
}
constexpr auto isconstantevaluated(auto x) {
if (std::is_constant_evaluated()) {
return ceval(x);
} else {
return static_cast<decltype(x)>(x + 2.);
}
}
int main() {
#ifdef __clang__ // GCC KO
auto a = constexprif(42);
const auto b = constexprif(42);
constexpr auto c = constexprif(42);
std::cout << "constexprif " << a << '\t' << b << '\t' << c << '\n';
#endif
auto d = constexprif_consteval(42);
const auto e = constexprif_consteval(42);
constexpr auto f = constexprif_consteval(42);
std::cout << "constexprif_consteval " << d << '\t' << e << '\t' << f
<< '\n';
auto g = constevalif(42);
const auto h = constevalif(42);
constexpr auto i = constevalif(42);
std::cout << "constevalif " << g << '\t' << h << '\t' << i << '\n';
// auto j = isconstantevaluated(42);
#ifdef __clang__ // GCC KO
const auto k = isconstantevaluated(42);
constexpr auto l = isconstantevaluated(42);
std::cout << "isconstantevaluated " << k << '\t' << l << '\n';
#endif
}
ceval
is a basic consteval
function, just to see in what context it can be called by a constexpr
function.
constexprif
is a function that (incorrectly) tried to return ceval
if its argument is integral. Obviously, calling it with a runtime argument will not compile.
constexprif_consteval
is a fixed version that will call ceval
only in a constant evaluate context.
constevalif
calls ceval
in a constant evaluate context and should have the same output as constexprif_consteval
for integral arguments.
Eventually, isconstantevaluated
is the same as constevalif
but with a if (std::is_constant_evaluated())
instead of an if consteval
.
The output is as follows.
For GCC:
constexprif_consteval 1765 1764 1764
constevalif 44 1764 1764
For Clang:
constexprif 1764 1764 1764
constexprif_consteval 1764 1764 1764
constevalif 1764 1764 1764
isconstantevaluated 1764 1764
I took note of this post about a Clang bug, but it's quite old now and I think that this question is going a bit deeper.
My expectations are the following.
I think that GCC rightfully refuses constexprif
in all situations because instantiating the function for an integral type may lead to use it in a runtime context.
Clang seems overzealous to accept it in all cases, only on the basis that the argument is known at compile time.
With constexprif_consteval
GCC seems to use the context (a non-const
initialization) to chose the not consteval
branch while Clang also goes for the full compile-time branch.
With constevalif
we're observing the same behavior.
Eventually isconstantevaluated
is always rejected by GCC, the if (std::is_constant_evaluated())
being a runtime test.
Thus my impression is that GCC is correct with respect to my understanding of the standard so far. Is it so? (yet, IMHO, the clang behavior seems easier to understand for me: arguments are known at compile-time? then the expression can be constant evaluated).
language-lawyer
is included. – Rheumatoid