The following program
template<class T>
consteval auto foo(const T&) {
return 0;
}
template<class T>
consteval auto bar(const T& t) {
auto n = foo(t);
return n;
}
int main() {
static_assert(foo("abc") == 0);
static_assert(bar("abc") == 0);
}
is built fine in GCC, but Clang rejects it with the messages:
error: call to consteval function 'foo<char[4]>' is not a constant expression
note: in instantiation of function template specialization 'bar<char[4]>' requested here
static_assert(bar("abc") == 0);
note: function parameter 't' with unknown value cannot be used in a constant expression
auto n = foo(t);
Demo: https://gcc.godbolt.org/z/M6GPnYdqb
Is it some bug in Clang?
This is a clang bug. gcc and msvc are correct to accept it.
There are two relevant rules in question:
All immediate invocations must be constant expressions. This comes from [expr.const]/13:
An expression or conversion is in an immediate function context if it is potentially evaluated and either:
- its innermost enclosing non-block scope is a function parameter scope of an immediate function, or
- its enclosing statement is enclosed ([stmt.pre]) by the compound-statement of a consteval if statement ([stmt.if]).
An expression or conversion is an immediate invocation if it is a potentially-evaluated explicit or implicit invocation of an immediate function and is not in an immediate function context. An immediate invocation shall be a constant expression.
And touching an unknown reference is not allowed in constant expressions (this is [expr.const]/5.13):
An expression E is a core constant expression unless the evaluation of E, following the rules of the abstract machine ([intro.execution]), would evaluate one of the following: [...]
- an id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization and either
- it is usable in constant expressions or
- its lifetime began within the evaluation of E;
For more on this latter rule, see my post on the the constexpr array size problem and my proposal to resolve this (hopefully for C++23).
Okay, back to the problem. foo
is obviously fine, it doesn't do anything.
In bar
, we call foo(t)
. This is not a constant expression (because t
is an unknown reference), but we are in an immediate function context (because bar
is consteval
), so it doesn't matter that foo(t)
is not a constant expression. All that matters is that bar("abc")
is a constant expression (since that is an immediate invocation), and there's no rule we're violating there. It is pretty subtle, but the reference t
here does have its lifetime begin within the evaluation of E
-- since E
here is the call bar("abc")
, not the call foo(t)
.
If you mark bar
constexpr
instead of consteval
, then the foo(t)
call inside of it becomes an immediate invocation, and now the fact that it is not a constant expression is relevant. All three compilers correctly reject in this case.