使用引用参数嵌套调用 consteval 函数

Nested call of consteval functions with a reference argument

下面的程序

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);
}

在 GCC 中构建良好,但 Clang 拒绝它并显示消息:

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);

演示:https://gcc.godbolt.org/z/M6GPnYdqb

是否是 Clang 中的一些错误?

这是一个 clang 错误。 gcc 和 msvc 接受是正确的。

有两个相关规则有问题:

所有立即调用必须是常量表达式。这来自 [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.

并且在常量表达式中不允许使用未知引用(这是 [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;

有关后一条规则的更多信息,请参阅我在 the constexpr array size problem and my proposal to resolve this 上的 post(希望适用于 C++23)。


好了,回到问题。 foo 显然没问题,它什么也没做。

bar中,我们调用foo(t)。这不是一个常量表达式(因为 t 是一个未知的引用),但是我们在一个直接的函数上下文中(因为 barconsteval),所以 consteval 并不重要=12=] 不是常量表达式。重要的是 bar("abc") 是一个常量表达式(因为 立即调用),我们没有违反那里的规则。这是非常微妙的,但是这里的引用 t 确实 E 的评估中开始了它的生命周期——因为这里的 E 是调用bar("abc")调用foo(t)

如果你标记barconstexpr而不是consteval,那么里面的foo(t)调用就变成了立即调用,现在事实是不是常量表达式是相关的。在这种情况下,所有三个编译器都正确地拒绝了。