使用引用参数嵌套调用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);
}
在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
是未知引用),但我们处于直接函数上下文中(因为bar
是consteval
),所以foo(t)
不是常量表达式并不重要。重要的是bar("abc")
是一个常量表达式(因为是立即调用),并且我们没有违反任何规则。这很微妙,但这里的引用t
确实在E
的求值范围内开始其生存期--因为E
这里是调用bar("abc")
,不是调用foo(t)
。
如果您标记bar
constexpr
而不是consteval
,则它内部的foo(t)
调用将成为立即调用,并且现在它不是常量表达式这一事实是相关的。在这种情况下,所有三个编译器都正确地拒绝。
相关文章