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.
consteval auto bar(const T& t)
intoconsteval auto bar(T t)
then it compiles for clang. Hope someone can have this clarified. – Fluidicsconstexpr
function on a reference notconstexpr
? – Tartconsteval
byconstexpr
works Demo. – Calvinism