70

ヘッダーファイルに次のように記述できますか?

inline void f () { std::function<void ()> func = [] {}; }

また

class C { std::function<void ()> func = [] {}; C () {} };

各ソースファイルでは、ラムダの型が異なる可能性があるため、std::function(target_typeの結果に含まれる型は異なります)。

これは ODR ( One Definition Rule ) 違反ですか? よくあるパターンのように見えますが、これは妥当な行為ですか? 2 番目のサンプルは毎回 ODR に違反しますか?それとも、少なくとも 1 つのコンストラクターがヘッダー ファイルにある場合にのみ違反しますか?

4

2 に答える 2

41

これは、ラムダの型が翻訳単位間で異なるかどうかに要約されます。もしそうなら、それはテンプレートの引数推定に影響を与え、異なる関数が呼び出される可能性があります - 一貫した定義であることを意味します。それは 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) に違反します。

于 2016-01-11T12:16:31.083 に答える
8

更新しました

結局、@Columboの回答に同意しますが、実用的な5セントを追加したい:)

ODR 違反は危険に聞こえますが、この特定のケースでは実際には深刻な問題ではありません。異なる TU で作成されたラムダ クラスは、typeid を除いて同等です。したがって、ヘッダーで定義されたラムダ (またはラムダに依存する型) の typeid に対処する必要がない限り、安全です。

現在、ODR 違反がバグとして報告されると、問題のあるコンパイラ (MSVC や、おそらく Itanium ABI に従っていないその他のコンパイラ) で修正される可能性が高くなります。Itanium ABI 準拠のコンパイラ (gcc や clang など) は、ヘッダーで定義されたラムダの ODR に対応したコードを既に生成していることに注意してください。

于 2016-01-12T15:09:05.750 に答える