7

以下程序

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 中的一些错误吗?

4

1 回答 1

5

这是一个铿锵的错误。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)。这不是一个常量表达式(因为t它是一个未知的引用),但是我们在一个直接的函数上下文中(因为baris consteval),所以它foo(t)不是一个常量表达式并不重要。重要的是它bar("abc")是一个常量表达式(因为这一个立即调用),并且我们在那里没有违反任何规则。这是非常微妙的,但t这里的引用确实在评估中开始了它的生命周期E-- 因为E这里是 call bar("abc")而不是call foo(t)

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

于 2021-11-30T18:37:06.963 回答