ラムダキャプチャと同じ名前のパラメータ-誰が他をシャドウしている?(clangとgcc)


125
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 ++を使用しています!」と出力されます。キャプチャ fooが使用されていないことを警告します。

  • g ++ 4.9.0以降では「あなたはg ++を使用しています!」と出力します。パラメータ fooが使用されていないことを警告します

ここでC ++標準に正確に準拠しているコンパイラはどれですか?

ワンドボックスの例


1
wandboxからここにコードを貼り付けると(共有ボタンを忘れたようです)、VS2015(?)がclangの警告C4458: 'foo'の宣言によりクラスメンバーが非表示になっていると同意しているように見えます。
nwp 2017

12
素晴らしい例..
deviantfan 2017

4
ラムダにはテンプレート関数呼び出し演算子を持つ型があるため、ロジックによって、パラメーターはキャプチャされた変数をのようにシャドウする必要があると言われstruct Lambda { template<typename T> void operator()(T foo) const { /* ... */ } private: decltype(outer_foo) foo{outer_foo}; }ます。
skypjack

2
@nwp VSは間違っています。ラムダのデータメンバーには名前がないため、シャドウできません。標準では、「キャプチャされたエンティティへのアクセスは、対応するデータメンバーへのアクセスに変換されます」とあり、正方形のままです。
n。「代名詞」m。

10
私はclangのバージョンが正しいことを願っています。関数の外側の何かが関数のパラメーターをシャドウしているのではなく、その逆の場合、それは新境地を開くでしょう!
MM

回答:


65

更新:下部の引用でコアチェアによって約束されたように、コードは不正な形式になりました

場合識別子単純キャプチャとして現れる宣言-IDのパラメータのラムダ宣言パラメータ宣言句、プログラムが悪い形成されています。


少し前にラムダでの名前のルックアップに関していくつかの問題がありました。それらはN2927によって解決されました

新しい表現は、キャプチャーされたエンティティーの使用を再マップするためにルックアップに依存しなくなりました。ラムダの複合ステートメントが2つのパスで処理される、またはその複合ステートメント内の名前がクロージャー型のメンバーに解決される可能性があるという解釈をより明確に否定します。

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

ラムダ式化合物-文は生成機能・身体関数呼び出し演算子の([dcl.fct.def]を)が、名前検索の目的のために、[...]、化合物-文はの文脈で考えられていますラムダ式。[

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*
    }; 
  }
};

- 終了例 ]

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

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

キャプチャをinit-capture、つまりのfoo = ""代わりにするfoo場合、答えは明確ではありません。これは、キャプチャが「ブロック」が指定されていない宣言を実際に引き起こすためです。これについてコアチェアにメッセージを送った。

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


私がここでバラバラにすることは何もありません:) 単純なキャプチャは何も宣言しません。そのため、名前ルックアップの正しい結果はかなり明白です(明示的なキャプチャの代わりにキャプチャデフォルトを使用すると、GCCはそれを正しく行います)。init-captureは少しトリッキーです。
TC

1
@TC同意する。私はコアの問題を提出しましたが、明らかにこれはすでに議論されています、編集された回答を参照してください。
コロンボ2017

6

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

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

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

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

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

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

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


上で説明したように、このコンテキストでのルックアップは、変換されたクロージャタイプにいるかのように実行されることはありません。
コロンボ2017

@Columbo推論から明らかであっても見逃した行を追加しています。つまり、clangは正しいということです。面白いのは、答えを出そうとしているときに[expr.prim.lambda] / 8を見つけたということですが、あなたのように適切に使用することができませんでした。だからこそ、あなたの答えを読むのはいつも喜びです。;-)
skypjack
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.