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

Fed*_*dor 8 c++ language-lawyer c++20 consteval

下面的程序

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);
}
Run Code Online (Sandbox Code Playgroud)

在 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);
Run Code Online (Sandbox Code Playgroud)

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

这是 Clang 中的一些错误吗?

Bar*_*rry 6

这是一个 clang bug。gcc 和 msvc 接受它是正确的。

有两个相关规则有问题:

所有立即调用都必须是常量表达式。这来自[expr.const]/13

如果表达式或转换可能被求值并且满足以下任一条件,则该表达式或转换位于立即函数上下文中:

  • 其最内层封闭的非块作用域是立即函数的函数参数作用域,或者
  • 它的封闭语句被 consteval if 语句 ([stmt.if]) 的复合语句括起来 ([stmt.pre])。

如果表达式或转换是立即函数的潜在评估显式或隐式调用并且不在立即函数上下文中,则它是立即调用。立即调用应是常量表达式。

并且在常量表达式中不允许接触未知引用(这是[expr.const]/5.13):

表达式 E 是核心常量表达式,除非对 E 的求值遵循抽象机 ([intro.execution]) 的规则,将求值以下其中一项:[...]

  • 引用引用类型的变量或数据成员的 id 表达式,除非引用具有前面的初始化并且
    • 它可用于常量表达式或
    • 它的生命周期从E的评估开始;

有关后一条规则的更多信息,请参阅我关于constexpr 数组大小问题的帖子以及我解决此问题的建议(希望适用于 C++23)。


好吧,回到问题上来。foo显然很好,它没有做任何事情。

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

如果您标记bar constexpr而不是consteval,那么foo(t)它内部的调用将成为立即调用,并且现在它不是常量表达式这一事实是相关的。在这种情况下,所有三个编译器都正确拒绝。