14

特别是 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 是被邪恶力量诅咒了,它有错误吗,还是问题现在就在我的椅子上?

4

3 回答 3

11

在转换为使用函数符号转换时,您需要一组额外的括号void,否则括号被认为是转换表达式而不是折叠表达式的一部分。折叠表达式语法本身需要一组括号。

以下所有工作都不会产生任何警告:

void((std::cout << ... << args));
(void)((std::cout << ... << args));

或者只是调用一些ostream成员函数来避免未使用的结果警告

(std::cout << ... << args).flush();

正如 TC 在下面的评论中提到的那样,这种行为(void)(std::cout << ... << args);似乎是一个 clang 错误。转换符号的语法在5.4 [expr.cast]中指定

cast-expression :
  一元表达式
  ( type-id ) cast-expression

由于括号不需要作为强制转换表达式的一部分,因此该用法不应该产生警告,更重要的是,它应该导致打印参数。

于 2015-08-12T18:31:49.437 回答
3

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

于 2016-11-14T04:29:50.003 回答
2

来自 [expr.prim.fold] 的折叠表达式是:

折叠表达式对二元运算符执行模板参数包 (14.5.3) 的折叠。
    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- 只是里面的括号集是一部分语法,所以你需要两个

void((std::cout << ... << args));

或者你可以只添加一个额外的endl或类似的东西:

(std::cout << ... << args) << std::endl;

或者返回结果:

template <class... Args>
std::ostream& foo(Args... args) {
    return (std::cout << ... << args);
}
于 2015-08-12T18:41:07.207 回答