GCC 和 Clang 不同意 lambda 的 constexpr-ness?
GCC and Clang disagree about constexpr-ness of lambda?
为什么 Clang 无法编译以下代码,并显示表达式不是 constexpr 的消息,而 GCC 为什么不能?哪个编译器是正确的?
https://godbolt.org/z/nUhszh
(显然,这只是一个示例。事实上,我确实需要能够在 constexpr 上下文中调用 constexpr 函数对象。)
#include <type_traits>
template <typename Predicate>
constexpr int f(Predicate&& pred) {
if constexpr (pred(true)) {
return 1;
}
else {
return 0;
}
}
int main() {
f([](auto m) {
return std::is_same_v<decltype(m), bool>;
});
}
clang 8.0.0 的输出 -std=c++17 -stdlib=libc++ -O1 -march=skylake
:
<source>:5:19: error: constexpr if condition is not a constant expression
if constexpr (pred(true)) {
^
<source>:14:5: note: in instantiation of function template specialization 'f<(lambda at <source>:14:7)>' requested here
f([](auto m) {
^
1 error generated.
Compiler returned: 1
通过在 if constexpr
中使用谓词,您希望它是无条件的常量表达式。但是 constexpr
函数可以 总是 在 non-constexpr 上下文中调用,参数不是常量表达式。因此函数参数可能永远不会被假定为常量表达式。
因此,GCC 接受未经修改的代码是错误的。
碰巧您的特定示例不需要 if constexpr
在 constexpr 上下文中工作。修改函数:
template <typename Predicate>
constexpr int f(Predicate&& pred) {
if (pred(true)) {
return 1;
}
else {
return 0;
}
}
当需要常量表达式时,可由 both compilers 调用:
int main() {
constexpr int i = f([](auto m) constexpr {
return std::is_same_v<decltype(m), bool>;
});
return i;
}
铿锵是对的。在表达式pred(true)
中,id-expressionpred
表示一个引用类型的变量。引用类型的变量只能出现在常量表达式中,前提是它们由常量表达式初始化,或者如果它们的初始化是在表达式求值期间执行的 ([expr.const]/2.11).
所以pred(true)
不是常量表达式。
如果将参数pred
的声明改为Predicate pred
,Pred
将不是引用类型。表达式 pred(true)
将等同于 pred.operator()(true)
。 pred
将是 object-expression 在 class member access expression, and as such, no lvalue-to-rvalue conversion will be applied to pred
. So the id-expression pred
will not have to be initialized by a constant expression (see [expr.const]/2.7.2) in order to be a constant expression. Then the function call is a constant expression because the call operator is implictly a constexpr function [expr.prim.lambda.closure]/4.
这些事实证明了@NikosC 的提议。将 Pred
声明为 Predicate Pred
。在这种情况下,您的代码将同时使用 Clang 和 Gcc 进行编译,并且符合标准。
为什么 Clang 无法编译以下代码,并显示表达式不是 constexpr 的消息,而 GCC 为什么不能?哪个编译器是正确的? https://godbolt.org/z/nUhszh (显然,这只是一个示例。事实上,我确实需要能够在 constexpr 上下文中调用 constexpr 函数对象。)
#include <type_traits>
template <typename Predicate>
constexpr int f(Predicate&& pred) {
if constexpr (pred(true)) {
return 1;
}
else {
return 0;
}
}
int main() {
f([](auto m) {
return std::is_same_v<decltype(m), bool>;
});
}
clang 8.0.0 的输出 -std=c++17 -stdlib=libc++ -O1 -march=skylake
:
<source>:5:19: error: constexpr if condition is not a constant expression
if constexpr (pred(true)) {
^
<source>:14:5: note: in instantiation of function template specialization 'f<(lambda at <source>:14:7)>' requested here
f([](auto m) {
^
1 error generated.
Compiler returned: 1
通过在 if constexpr
中使用谓词,您希望它是无条件的常量表达式。但是 constexpr
函数可以 总是 在 non-constexpr 上下文中调用,参数不是常量表达式。因此函数参数可能永远不会被假定为常量表达式。
因此,GCC 接受未经修改的代码是错误的。
碰巧您的特定示例不需要 if constexpr
在 constexpr 上下文中工作。修改函数:
template <typename Predicate>
constexpr int f(Predicate&& pred) {
if (pred(true)) {
return 1;
}
else {
return 0;
}
}
当需要常量表达式时,可由 both compilers 调用:
int main() {
constexpr int i = f([](auto m) constexpr {
return std::is_same_v<decltype(m), bool>;
});
return i;
}
铿锵是对的。在表达式pred(true)
中,id-expressionpred
表示一个引用类型的变量。引用类型的变量只能出现在常量表达式中,前提是它们由常量表达式初始化,或者如果它们的初始化是在表达式求值期间执行的 ([expr.const]/2.11).
所以pred(true)
不是常量表达式。
如果将参数pred
的声明改为Predicate pred
,Pred
将不是引用类型。表达式 pred(true)
将等同于 pred.operator()(true)
。 pred
将是 object-expression 在 class member access expression, and as such, no lvalue-to-rvalue conversion will be applied to pred
. So the id-expression pred
will not have to be initialized by a constant expression (see [expr.const]/2.7.2) in order to be a constant expression. Then the function call is a constant expression because the call operator is implictly a constexpr function [expr.prim.lambda.closure]/4.
这些事实证明了@NikosC 的提议。将 Pred
声明为 Predicate Pred
。在这种情况下,您的代码将同时使用 Clang 和 Gcc 进行编译,并且符合标准。