Clang 和二进制折叠表达式——空参数包的诅咒
Clang and the binary fold expressions — The curse of the empty parameter pack
特别是 Clang 3.6.0,目前由 Coliru 托管。
所有这些片段都是从 :
调用的
int main() {
foo();
std::cout << "\n----\n";
foo(1, 2, 3);
}
以下代码:
template <class... Args>
void foo(Args... args) {
std::cout << ... << args;
}
触发以下编译错误:
main.cpp:7:17: error: expected ';' after expression
std::cout << ... << args;
^
;
main.cpp:7:15: error: expected expression
std::cout << ... << args;
^
所以我尝试在表达式两边加上括号:
(std::cout << ... << args);
有效,但会触发警告:
main.cpp:7:6: warning: expression result unused [-Wunused-value]
(std::cout << ... << args);
^~~~~~~~~
main.cpp:11:5: note: in instantiation of function template specialization 'foo<>' requested here
foo();
^
所以我尝试丢弃表达式的值,将函数样式强制转换为 void
:
void(std::cout << ... << args);
但是:
main.cpp:7:20: error: expected ')'
void(std::cout << ... << args);
^
main.cpp:7:9: note: to match this '('
void(std::cout << ... << args);
^
我也试了 static_cast
,结果一样。
所以我尝试改用 C-cast :
(void)(std::cout << ... << args);
但是然后:
main.cpp:6:18: warning: unused parameter 'args' [-Wunused-parameter]
void foo(Args... args) {
^
... 我的输出只有 ----
: foo(1, 2, 3);
不再输出了!
Clang 是否被未来标准的邪恶力量诅咒,它是否有错误,或者现在我的椅子上有问题?
使用函数符号 cast 转换为 void
时需要一组额外的括号,否则括号将被视为转换表达式的一部分,而不是折叠表达式。 fold expression syntax 本身需要一组括号。
以下所有工作都不会产生任何警告:
void((std::cout << ... << args));
(void)((std::cout << ... << args));
或者只是调用一些ostream
成员函数来避免未使用的结果警告
(std::cout << ... << args).flush();
作为T.C。在下面的评论中提到, (void)(std::cout << ... << args);
的行为似乎是一个 clang 错误。转换符号的语法在 5.4 [expr.cast]
中指定
cast-expression:
unary-expression
( type-id ) cast-expression
由于括号不是强制转换表达式的一部分,因此该用法不应产生警告,更重要的是,它应该会导致打印参数。
来自[expr.prim.fold]的折叠表达式是:
A fold expression performs a fold of a template parameter pack (14.5.3) over a binary operator.
fold-expression:
( cast-expression fold-operator ... )
( ... fold-operator cast-expression )
( cast-expression fold-operator ... fold-operator cast-expression )
请注意,在所有情况下,括号都是语法的一部分。所以你最初的例子在语法上是不正确的,必须是:
template <class... Args>
void foo(Args... args) {
(std::cout << ... << args);
}
这会在空包的情况下给你一个警告,因为二进制折叠减少到 std::cout;
要摆脱那个警告,你可以按照通常的方式转换为 void
- 只是内括号是语法的一部分所以你需要 two:
void((std::cout << ... << args));
或者您可以添加一个额外的 endl
或类似的东西:
(std::cout << ... << args) << std::endl;
或return结果:
template <class... Args>
std::ostream& foo(Args... args) {
return (std::cout << ... << args);
}
我决定仔细研究一下 Clang 源代码中的这个错误。这是有问题的代码部分。这种情况发生在它刚刚完成解析 (<type>)
并且现在正在解析以下带括号的表达式时:
} else if (isTypeCast) {
// Parse the expression-list.
InMessageExpressionRAIIObject InMessage(*this, false);
ExprVector ArgExprs;
CommaLocsTy CommaLocs;
if (!ParseSimpleExpressionList(ArgExprs, CommaLocs)) {
// FIXME: If we ever support comma expressions as operands to
// fold-expressions, we'll need to allow multiple ArgExprs here.
if (ArgExprs.size() == 1 && isFoldOperator(Tok.getKind()) &&
NextToken().is(tok::ellipsis))
return ParseFoldExpression(Result, T);
ExprType = SimpleExpr;
Result = Actions.ActOnParenListExpr(OpenLoc, Tok.getLocation(),
ArgExprs);
}
}
// The beginning of ParseFoldExpression(LHS, T):
if (LHS.isInvalid()) {
T.skipToEnd();
return true;
}
导致这个错误的代码的具体部分在这里:
return ParseFoldExpression(Result, T);
事实证明,Result
永远不会与其初始值 true
分开。我认为它应该设置为 ArgExprs.front()
,现在为 std::cout
。
现在您也会注意到 FIXME。虽然与此问题无关,但可能值得一并解决。
作为我的第一个 Clang 修复程序,在提交更改之前我还有几件事要做(作为参考,Clang 4.0 目前正在开发中)。我会非常高兴能够解决这个问题,无论是我还是其他人。至少,我的发现暂时记录在某处。
特别是 Clang 3.6.0,目前由 Coliru 托管。
所有这些片段都是从 :
调用的int main() {
foo();
std::cout << "\n----\n";
foo(1, 2, 3);
}
以下代码:
template <class... Args>
void foo(Args... args) {
std::cout << ... << args;
}
触发以下编译错误:
main.cpp:7:17: error: expected ';' after expression
std::cout << ... << args;
^
;
main.cpp:7:15: error: expected expression
std::cout << ... << args;
^
所以我尝试在表达式两边加上括号:
(std::cout << ... << args);
有效,但会触发警告:
main.cpp:7:6: warning: expression result unused [-Wunused-value]
(std::cout << ... << args);
^~~~~~~~~
main.cpp:11:5: note: in instantiation of function template specialization 'foo<>' requested here
foo();
^
所以我尝试丢弃表达式的值,将函数样式强制转换为 void
:
void(std::cout << ... << args);
但是:
main.cpp:7:20: error: expected ')'
void(std::cout << ... << args);
^
main.cpp:7:9: note: to match this '('
void(std::cout << ... << args);
^
我也试了 static_cast
,结果一样。
所以我尝试改用 C-cast :
(void)(std::cout << ... << args);
但是然后:
main.cpp:6:18: warning: unused parameter 'args' [-Wunused-parameter]
void foo(Args... args) {
^
... 我的输出只有 ----
: foo(1, 2, 3);
不再输出了!
Clang 是否被未来标准的邪恶力量诅咒,它是否有错误,或者现在我的椅子上有问题?
使用函数符号 cast 转换为 void
时需要一组额外的括号,否则括号将被视为转换表达式的一部分,而不是折叠表达式。 fold expression syntax 本身需要一组括号。
以下所有工作都不会产生任何警告:
void((std::cout << ... << args));
(void)((std::cout << ... << args));
或者只是调用一些ostream
成员函数来避免未使用的结果警告
(std::cout << ... << args).flush();
作为T.C。在下面的评论中提到, (void)(std::cout << ... << args);
的行为似乎是一个 clang 错误。转换符号的语法在 5.4 [expr.cast]
cast-expression:
unary-expression
( type-id ) cast-expression
由于括号不是强制转换表达式的一部分,因此该用法不应产生警告,更重要的是,它应该会导致打印参数。
来自[expr.prim.fold]的折叠表达式是:
A fold expression performs a fold of a template parameter pack (14.5.3) over a binary operator.
fold-expression:
( cast-expression fold-operator ... )
( ... fold-operator cast-expression )
( cast-expression fold-operator ... fold-operator cast-expression )
请注意,在所有情况下,括号都是语法的一部分。所以你最初的例子在语法上是不正确的,必须是:
template <class... Args>
void foo(Args... args) {
(std::cout << ... << args);
}
这会在空包的情况下给你一个警告,因为二进制折叠减少到 std::cout;
要摆脱那个警告,你可以按照通常的方式转换为 void
- 只是内括号是语法的一部分所以你需要 two:
void((std::cout << ... << args));
或者您可以添加一个额外的 endl
或类似的东西:
(std::cout << ... << args) << std::endl;
或return结果:
template <class... Args>
std::ostream& foo(Args... args) {
return (std::cout << ... << args);
}
我决定仔细研究一下 Clang 源代码中的这个错误。这是有问题的代码部分。这种情况发生在它刚刚完成解析 (<type>)
并且现在正在解析以下带括号的表达式时:
} else if (isTypeCast) {
// Parse the expression-list.
InMessageExpressionRAIIObject InMessage(*this, false);
ExprVector ArgExprs;
CommaLocsTy CommaLocs;
if (!ParseSimpleExpressionList(ArgExprs, CommaLocs)) {
// FIXME: If we ever support comma expressions as operands to
// fold-expressions, we'll need to allow multiple ArgExprs here.
if (ArgExprs.size() == 1 && isFoldOperator(Tok.getKind()) &&
NextToken().is(tok::ellipsis))
return ParseFoldExpression(Result, T);
ExprType = SimpleExpr;
Result = Actions.ActOnParenListExpr(OpenLoc, Tok.getLocation(),
ArgExprs);
}
}
// The beginning of ParseFoldExpression(LHS, T):
if (LHS.isInvalid()) {
T.skipToEnd();
return true;
}
导致这个错误的代码的具体部分在这里:
return ParseFoldExpression(Result, T);
事实证明,Result
永远不会与其初始值 true
分开。我认为它应该设置为 ArgExprs.front()
,现在为 std::cout
。
现在您也会注意到 FIXME。虽然与此问题无关,但可能值得一并解决。
作为我的第一个 Clang 修复程序,在提交更改之前我还有几件事要做(作为参考,Clang 4.0 目前正在开发中)。我会非常高兴能够解决这个问题,无论是我还是其他人。至少,我的发现暂时记录在某处。