14

具体的には、現在 Coliru によってホストされている Clang 3.6.0 です。

これらのスニペットはすべて次から呼び出されます。

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キャストで試しました:

(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]で指定されています。

キャスト式:
  単項式
  ( type-id ) キャスト式

キャスト式の一部として括弧は必要ないため、その使用によって警告が生成されることはなく、さらに重要なことに、引数が出力されるはずです。

于 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] からの折り畳み式は次のとおりです。

折り畳み式は、2 項演算子に対してテンプレート パラメーター パック (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、内側の括弧のセットが文法なので、 2つ必要です:

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 に答える