问题 Clang和二进制折叠表达式 - 空参数包的诅咒


特别是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是否受到未来标准的邪恶力量的诅咒,是否有虫子,或者现在坐在我的椅子上的问题是什么?


8018
2017-08-12 18:18


起源

我无法在任何编译器中编译它。尝试过MSVC2015和 gcc.godbolt.org - NathanOliver
static_cast<void>((std::cout << ... << args)); 似乎工作(即双parens),我的猜测是clang是正确的,因为折叠表达需要它自己的一对parentesis - Piotr Skotnicki
我和cin有相同的设置。如果我记得问题是它以某种方式试图扩展 (cin << (1 << 2)) 代替 ((cin >> 1)>> 2) - bolov


答案:


在转换为时,您需要一组额外的括号 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

由于括号不是铸造表达式的一部分,因此该用法不应产生警告,更重要的是,它应该导致打印参数。


10
2017-08-12 18:31



我不明白为什么 (void)(std::cout << ... << args); 但是没有用。那部分对我来说就像一个小虫。 - T.C.
报道为 llvm.org/bugs/show_bug.cgi?id=24440 - T.C.
那么答案就是“全部三个”。非常感谢,Praetorian的回答和T.C.对于错误报告:) - Quentin


我决定更好地看看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目前正在开发中)。无论是我还是其他人,我都会非常高兴能够解决这个问题。至少,我的发现现在在某处记录。


3
2017-11-14 04:29





来自[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);
}

2
2017-08-12 18:41