【问题标题】:Clang and the binary fold expressions — The curse of the empty parameter packClang 和二进制折叠表达式——空参数包的诅咒
【发布时间】:2015-08-12 18:18:38
【问题描述】:

特别是 Clang 3.6.0,目前由 Coliru 托管。

所有这些 sn-ps 都是从以下位置调用的:

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 是不是被未来标准的邪恶力量诅咒了,它有错误,还是现在我的椅子上有问题?

【问题讨论】:

  • 我无法在任何编译器中编译它。试过 MSVC2015 和gcc.godbolt.org
  • static_cast&lt;void&gt;((std::cout &lt;&lt; ... &lt;&lt; args)); 似乎有效(即双括号),我猜clang是正确的,因为折叠表达式需要它自己的一对括号
  • 我对 cin 进行了相同的设置。如果我记得问题是它试图以某种方式扩展 (cin &lt;&lt; (1 &lt;&lt; 2)) 而不是 ((cin &gt;&gt; 1)&gt;&gt; 2)

标签: c++ clang compiler-bug c++17 fold-expression


【解决方案1】:

在使用函数符号强制转换为void 时,您需要一组额外的括号,否则括号将被视为强制转换表达式而不是折叠表达式的一部分。 fold expression syntax 本身需要一组括号。

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

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

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

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

作为 T.C.在下面的 cmets 中提到,(void)(std::cout &lt;&lt; ... &lt;&lt; args); 的行为似乎是一个 clang 错误。转换符号的语法在 5.4 [expr.cast]

中指定

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

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

【讨论】:

  • 我不明白为什么(void)(std::cout &lt;&lt; ... &lt;&lt; args); 不起作用。这部分在我看来像是一个错误。
  • 那么答案是“所有三个”。非常感谢 Praetorian 的回答和 T.C.对于错误报告:)
【解决方案2】:

我决定更好地查看 Clang 源代码中的这个错误。这是有问题的代码部分。这种情况发生在它刚刚完成解析 (&lt;type&gt;) 并正在解析以下括号表达式时:

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

【讨论】:

    【解决方案3】:

    一个折叠表达式,来自 [expr.prim.fold] 是:

    折叠表达式通过二元运算符执行模板参数包 (14.5.3) 的折叠。
    折叠表达式
    ( 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);
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-01-12
      • 2018-01-16
      • 2023-03-24
      • 1970-01-01
      • 2022-08-14
      • 2021-10-31
      • 1970-01-01
      相关资源
      最近更新 更多