これは、ラムダの型が翻訳単位間で異なるかどうかに要約されます。もしそうなら、それはテンプレートの引数推定に影響を与え、異なる関数が呼び出される可能性があります - 一貫した定義であることを意味します。それは ODR に違反します (以下を参照)。
ただし、それは意図したものではありません。実際、この問題はコアイシュー 765によって少し前にすでに触れられており、具体的には次のような外部リンケージを持つインライン関数に名前が付けられていf
ます。
7.1.2 [dcl.fct.spec] パラグラフ 4 は、外部リンケージを持つインライン関数の本体に現れるローカル静的変数と文字列リテラルは、プログラム内のすべての翻訳単位で同じエンティティでなければならないことを指定しています。ただし、ローカル型が同様に同じである必要があるかどうかについては何も述べられていません。
準拠しているプログラムは常に typeid を使用してこれを決定できますが、最近の C++ への変更 (ローカル型をテンプレート型引数として許可すること、ラムダ式クロージャー クラス) により、この問題はより差し迫ったものになっています。
2009 年 7 月の会議のメモ:
タイプは同じであることを意図しています。
現在、解決策により、次の文言が[dcl.fct.spec]/4に組み込まれています。
extern inline
関数の本体内で定義された型は、すべての翻訳単位で同じ型です。
(注: MSVC は、次のリリースでは可能性がありますが、上記の文言についてはまだ考慮していません)。
クロージャー型の定義は実際にブロック スコープ ( [expr.prim.lambda]/3 ) にあるため、そのような関数の本体内のラムダは安全です。
したがって、 の複数の定義が明確にf
定義されてきました。
ラムダ、特に関数テンプレートを利用できる外部リンケージを持つエンティティにはさらに多くの種類があるため、この解決策は確かにすべてのシナリオをカバーしているわけではありません - これは別のコアイシューでカバーする必要があります。
それまでの間、Itanium には、そのようなラムダの型がより多くの状況で確実に一致するようにするための適切なルールが既に含まれているため、Clang と GCC はすでにほとんど意図したとおりに動作するはずです。
異なるクロージャ タイプが ODR 違反である理由についての標準は次のとおりです。[basic.def.odr]/6の箇条書き (6.2) と (6.4) を検討してください。
[…] の定義は複数存在する可能性があります。そのようなエンティティD
が複数の翻訳単位で定義されている場合、各定義はD
同じ一連のトークンで構成されます。と
(6.2) - の各定義においてD
、[basic.lookup] に従って検索された対応する名前は、 の定義内で定義されたエンティティをD
参照するか、オーバーロードの解決後に同じエンティティを参照するものとします ([over.match] )部分的なテンプレートの特殊化 ([temp.over]) の一致後、[…]; と
(6.4) - の各定義ではD
、参照されるオーバーロードされた演算子、変換関数、コンストラクター、演算子の新しい関数、および演算子の削除関数への暗黙の呼び出しは、同じ関数、または の定義内で定義された関数を参照する必要があり
ます。[…]D
これが実質的に意味することは、エンティティの定義で呼び出される関数はすべての翻訳単位で同じである、またはローカル クラスとそのメンバーのようにその定義内で定義されているということです。つまり、ラムダの使用自体は問題ありませんが、関数テンプレートに渡すことは明らかに問題です。これらは定義の外で定義されているためです。
の例でC
は、クロージャータイプはクラス内で定義されています(そのスコープは最小の囲みです)。クロージャー型が 2 つの TU で異なる場合、標準が意図せずにクロージャー型の一意性を暗示する可能性がある場合、コンストラクターはfunction
のコンストラクター テンプレートの異なる特殊化をインスタンス化して呼び出し、上記の引用の (6.4) に違反します。