web-dev-qa-db-ja.com

正のラムダ: '+ [] {}'-これはどのような魔術ですか?

Stack Overflowの質問C++ 11で許可されていないラムダの再定義、なぜ?、コンパイル:

int main() {
    auto test = []{};
    test = []{};
}

質問に答えられ、すべてがうまくいくように見えました。そして Johannes Schaub が来て 興味深い観察 を作りました:

最初のラムダの前に+を置くと、魔法のように動き始めます。

だから私は興味があります:なぜ次のように動作しますか?

int main() {
    auto test = +[]{}; // Note the unary operator + before the lambda
    test = []{};
}

GCC 4.7+と Clang 3.2+の両方で問題なくコンパイルできます。コード標準は準拠していますか?

254
Daniel Frey

はい、コードは標準に準拠しています。 +は、ラムダの単純な古い関数ポインターへの変換をトリガーします。

これは何ですか:

コンパイラは最初のラムダ([]{})を見て、§5.1.2に従ってクロージャオブジェクトを生成します。ラムダは非キャプチャラムダであるため、以下が適用されます。

5.1.2ラムダ式[expr.prim.lambda]

6 lambda-expressionlambda-captureのないクロージャータイプクロージャ型の関数呼び出し演算子と同じパラメーターと戻り値型を持つ関数へのポインターへのパブリック非仮想非明示const変換関数。この変換関数によって返される値は、呼び出されたときに、クロージャータイプの関数呼び出し演算子を呼び出すのと同じ効果を持つ関数のアドレスでなければなりません。

単項演算子+には組み込みのオーバーロードのセット、具体的には次のものがあるため、これは重要です。

13.6組み込み演算子[over.built]

8 すべてのタイプTには、次の形式の候補演算子関数が存在します。

T* operator+(T*);

これにより、何が起こるかが非常に明確になります。演算子+がクロージャーオブジェクトに適用されると、オーバーロードされた組み込み候補のセットには任意のポインターへの変換が含まれ、クロージャータイプには正確に1つの候補が含まれます:ラムダの関数ポインターへの変換。

したがって、auto test = +[]{};testのタイプは、void(*)()に推定されます。これで2行目は簡単になりました。2番目のラムダ/クロージャオブジェクトでは、関数ポインタへの割り当てにより、1行目と同じ変換がトリガーされます。 2番目のラムダは異なるクロージャタイプを持っていますが、結果の関数ポインタはもちろん互換性があり、割り当てることができます。

234
Daniel Frey