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

2022-05-16 00:00:00 language-lawyer c++ consteval c++20

以下程序

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:

表达式或转换位于立即函数上下文中,如果它可能被求值,并且:

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

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

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

表达式E是核心常量表达式,除非E的计算遵循抽象机的规则([Intro.Execution])将计算下列内容之一:[...]

  • 引用引用类型的变量或数据成员的id表达式,除非引用具有先前的初始化并且
    • 可用于常量表达式或
    • 其生存期在E的求值范围内开始;

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


好了,回到问题上。foo显然很好,它不会做任何事情。

bar中,我们调用foo(t)。这不是常量表达式(因为t是未知引用),但我们处于直接函数上下文中(因为barconsteval),所以foo(t)不是常量表达式并不重要。重要的是bar("abc")是一个常量表达式(因为是立即调用),并且我们没有违反任何规则。这很微妙,但这里的引用t确实在E的求值范围内开始其生存期--因为E这里是调用bar("abc"),不是调用foo(t)

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

相关文章