使用引用参数嵌套调用 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
是一个未知的引用),但是我们在一个直接的函数上下文中(因为 bar
是 consteval
),所以 consteval
并不重要=12=] 不是常量表达式。重要的是 bar("abc")
是一个常量表达式(因为 是 立即调用),我们没有违反那里的规则。这是非常微妙的,但是这里的引用 t
确实 在 E
的评估中开始了它的生命周期——因为这里的 E
是调用bar("abc")
,不调用foo(t)
。
如果你标记bar
constexpr
而不是consteval
,那么里面的foo(t)
调用就变成了立即调用,现在事实是不是常量表达式是相关的。在这种情况下,所有三个编译器都正确地拒绝了。
下面的程序
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
是一个未知的引用),但是我们在一个直接的函数上下文中(因为 bar
是 consteval
),所以 consteval
并不重要=12=] 不是常量表达式。重要的是 bar("abc")
是一个常量表达式(因为 是 立即调用),我们没有违反那里的规则。这是非常微妙的,但是这里的引用 t
确实 在 E
的评估中开始了它的生命周期——因为这里的 E
是调用bar("abc")
,不调用foo(t)
。
如果你标记bar
constexpr
而不是consteval
,那么里面的foo(t)
调用就变成了立即调用,现在事实是不是常量表达式是相关的。在这种情况下,所有三个编译器都正确地拒绝了。