特别是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是否受到未来标准的邪恶力量的诅咒,是否有虫子,或者现在坐在我的椅子上的问题是什么?
在转换为时,您需要一组额外的括号 void
使用功能表示法转换,否则括号被认为是转换表达式的一部分而不是折叠表达式。该 折叠表达式语法 本身需要一组括号。
以下所有工作均未发出任何警告:
void((std::cout << ... << args));
(void)((std::cout << ... << args));
或者只是打个电话 ostream
成员函数,以避免未使用的结果警告
(std::cout << ... << args).flush();
作为T.C.在下面的评论中提及,行为 (void)(std::cout << ... << args);
好像是一个铿锵的小虫。演员表示法的语法在。中指定 5.4 [expr.cast]
铸表达:
一元表达式
(type-id)cast-expression
由于括号不是铸造表达式的一部分,因此该用法不应产生警告,更重要的是,它应该导致打印参数。
我决定更好地看看Clang源代码中的这个bug。这是违规的代码部分。这种情况发生在它刚刚完成解析时 (<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;
}
负责此bug的代码的具体部分如下:
return ParseFoldExpression(Result, T);
事实证明 Result
从来没有脱离它的最初 true
值。我相信它应该被设定为 ArgExprs.front()
现在持有 std::cout
。
现在你也会注意到FIXME。虽然特别是与这个问题无关,但可能值得一提。
作为我的第一个Clang修复程序,在提交更改之前我还有几件事要做(作为参考,Clang 4.0目前正在开发中)。无论是我还是其他人,我都会非常高兴能够解决这个问题。至少,我的发现现在在某处记录。
来自[expr.prim.fold]的折叠表达式是:
折叠表达式在二元运算符上执行模板参数包(14.5.3)的折叠。
折叠式表达:
( 铸表达 折叠操作 ...)
(... 折叠操作 铸表达 )
( 铸表达 折叠操作 ... 折叠操作 铸表达 )
请注意,在所有情况下,括号都是语法的一部分。所以你的初始例子在语法上是不正确的,必须是:
template <class... Args>
void foo(Args... args) {
(std::cout << ... << args);
}
然后,在空包装的情况下,这会给你一个警告,因为二进制折叠减少到正好 std::cout;
要摆脱那个警告,你可以去通常的投射路线 void
- 只是内部括号组是语法的一部分,所以你需要 二:
void((std::cout << ... << args));
或者你可以多加一点 endl
或类似的:
(std::cout << ... << args) << std::endl;
或者返回结果:
template <class... Args>
std::ostream& foo(Args... args) {
return (std::cout << ... << args);
}