web-dev-qa-db-ja.com

同じ名前のLambdaキャプチャとパラメーター-誰が他をシャドウしますか? (clang vs gcc)

auto foo = "You're using g++!";
auto compiler_detector = [foo](auto foo) { std::puts(foo); };
compiler_detector("You're using clang++!");
  • clang ++ 3.6.0以降の印刷"clang ++!を使用しています!"およびcapturefooは未使用です。

  • g ++ 4.9.0以降の出力"あなたはg ++を使用しています!"で、パラメータについて警告しますfooは未使用です。

ここで、どのコンパイラがより正確にC++標準に従っていますか?

wandboxの例

123
Vittorio Romeo

更新:下部の引用でCore Chairによって約束されているように、コードは 現在は不正な形式

simple-captureidentifierdeclarator-idとして表示される場合lambda-declaratorparameter-declaration-clauseのパラメーターの場合、プログラムの形式が正しくありません。


少し前にラムダの名前検索に関する問題がいくつかありました。これらは N2927 によって解決されました。

新しい用語は、キャプチャされたエンティティの使用を再マップするためにルックアップに依存しなくなりました。 ラムダの複合文が2つのパスで処理されるか、その中の名前compound-statementは、クロージャー型のメンバーに解決される場合があります。

ルックアップは常にlambda-expressionのコンテキストで行われ、クロージャー型のメンバー関数本体への変換の「後」にはなりません。 [expr.prim.lambda]/8 を参照してください:

lambda-expressioncompound-statementは、function-bodyを生成します([dcl.fct.def])の関数呼び出し演算子ですが、名前検索[…]の目的のために、compound-statementlambda-expression。 [

struct S1 {
  int x, y;
  int operator()(int);
  void f() {
    [=]()->int {
      return operator()(this->x+y);  // equivalent to: S1::operator()(this->x+(*this).y)
                                     // and this has type S1*
    }; 
  }
};

end example]

(この例では、ルックアップでは、生成されたクロージャータイプのキャプチャメンバーが何らかの形で考慮されないことも明確になっています。)

名前fooは、キャプチャで(再)宣言されていません。ラムダ式を囲むブロックで宣言されています。パラメーターfooは、その外側のブロックにネストされているブロックで宣言されています( [basic.scope.block]/2 を参照してください。これはラムダパラメーターも明示的に言及しています)。ルックアップの順序は、明らかに 内部ブロックから外部ブロックへ です。したがって、パラメーターを選択する必要があります。つまり、Clangは正しいです。

キャプチャを初期キャプチャにする場合、つまりfoo = ""代わりにfooの場合、答えは明確ではありません。これは、キャプチャが実際に 宣言を引き起こす の「ブロック」が指定されていないためです。私はこれについて中心的な議長にメッセージを送りました。

これは問題2211です(新しい問題のリストがopen-std.orgサイトにまもなく表示されますが、残念ながら多くの問題のプレースホルダーがありますが、そのうちの1つです。コナの前にこれらのギャップを埋めるために一生懸命取り組んでいます。月末に会議)。 CWGはこれを1月の電話会議で議論しました。方向は、キャプチャ名がパラメータ名でもある場合、プログラムを不正な形式にすることです。

64
Columbo

意味のある回答を提供するために、質問に対するいくつかのコメントをまとめようとしています。
まず、次のことに注意してください。

  • 非静的データメンバーは、コピーキャプチャされた各変数のラムダに対して宣言されます
  • 特定のケースでは、ラムダにはfooという名前のパラメーターを受け入れるパブリックインラインテンプレート関数呼び出し演算子を持つクロージャータイプがあります

したがって、ロジックは、一見すると、パラメーターがキャプチャーされた変数を次のようにシャドウする必要があると言います。

struct Lambda {
    template<typename T> void operator()(T foo) const { /* ... */ }
    private: decltype(outer_foo) foo{outer_foo};
};

とにかく、@ n.m。コピーキャプチャされた変数に対して宣言された非静的データメンバーは実際には名前が付けられていないことを正しく指摘しました。そうは言っても、名前のないデータメンバーは引き続き識別子(foo)を使用してアクセスされます。したがって、関数呼び出し演算子のパラメーター名はまだ(言わせて)その識別子をシャドウである必要があります。
@ n.mが正しく指摘しているとおり。質問へのコメント:

元のキャプチャされたエンティティ[...]は、スコープルールに従って通常どおりにシャドウイングされる必要があります

そのため、clangは正しいと思います。

6
skypjack