c++23 introduces if consteval
, which is, as far as I understand, more or less a shortcut for c++20's std::is_constant_evaluated
// C++20
if (std::is_constant_evaluated())
{
}
else
{
}
// C++23
if consteval
{
}
else
{
}
A consequence of this is that the following does not compile:
[[nodiscard]] constexpr auto bar()
{
if consteval
{
return 2;
}
else
{
return "bar"; // clang: "inconsistent deduction for auto return type: 'int' and then 'const char*", analogue diagnostics from gcc and MSVC
}
}
This makes sense, because the following c++20 snippet doesn't compile either for the same obvious reason:
#include <type_traits>
[[nodiscard]] constexpr auto bar()
{
if(std::is_constant_evaluated())
{
return 2;
}
else
{
return "bar"; // Same diagnostic as above
}
}
However, in the case of if constexpr
, one is allowed to write things like the snippet below, which does compile:
template<bool b>
[[nodiscard]] constexpr auto bar()
{
if constexpr(b)
{
return 2;
}
else
{
return "bar";
}
}
My question is then:
What forbids such a thing in the Standard (or what makes it impossible)?
I assume this was to keep the same behaviour as std::is_constant_evaluated
, but again, what says this is the correct behaviour, and not something akin to the if constexpr
version?
DISCLAIMER :
I am not interested in workarounds. I'm interested in what forbids such a thing in the Standard (or what makes it impossible). I know that in this particular case, one obvious workaround is to specify the return
type to be something like std::variant<int, const char*>
, or any similar construct.
I am not arguing the first snippet in this question should compile either. I'm not sure if I want it to, actually.
It just makes it very confusing for the type system. Consider this:
constexpr auto f() {
if consteval {
return 1;
} else {
return 0;
}
}
constexpr int (*fp)() = &f;
int main() {
constexpr int i = fp();
int j = fp();
static_assert(i == 1);
assert(j == 0);
}
If the return types were different in each branch what would the function type be?
Sure, you could theoretically come up with a syntax for a function type that is dependent on if the call is constant-evaluated. Or something else like banning function pointers to such functions. Something like that just doesn't exist today.
As for what wording makes if consteval
behave differently from if constexpr
, it's actually because if constexpr
and deduced placeholder types have a special interaction:
If the value of the converted condition is
false
, the first substatement is a discarded statement, otherwise the second substatement, if present, is a discarded statement.
If the declared return type of the function contains a placeholder type, the return type of the function is deduced from non-discarded
return
statements, if any, in the body of the function ([stmt.if]).
And no such wording exists for if consteval
. So both return
statements are used to deduce the return type, which fails because they are of different types.
BTW, if consteval
does more than just if (std::is_constant_evaluated())
:
consteval int f(int x) { return x*x; }
constexpr void g(int x) {
if consteval {
f(x); // OK
}
if (std::is_constant_evaluated()) {
f(x); // Error: consteval function call does not form a constant expression and is not inside an immediate function or consteval if
}
}