web-dev-qa-db-ja.com

ヘッダーファイルでラムダを使用するとODRに違反する可能性がありますか?

以下をヘッダーファイルに書き込むことができますか?

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

または

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

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

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

67
Alex Telishev

これは、ラムダのタイプが翻訳単位間で異なるかどうかに要約されます。一致する場合、テンプレート引数の推論に影響し、異なる関数が呼び出される可能性があります-一貫した定義であることを意味します。これはODRに違反します(以下を参照)。

ただし、それは意図されていません。実際、この問題は core issue 765 によって少し前に既に触れられています。具体的には、fなどの外部リンケージでインライン関数に名前を付けます。

7.1.2 [dcl.fct.spec]パラグラフ4では、外部リンケージを備えたインライン関数の本体に表示されるローカルの静的変数と文字列リテラルは、プログラム内のすべての翻訳単位で同じエンティティでなければならないことを指定します。 ただし、ローカル型も同様に同じである必要があるかどうかについては言及されていません。

適合プログラムは常にtypeidを使用してこれを決定できますが、C++の最近の変更(テンプレート型引数としてローカル型を許可、ラムダ式クロージャークラス)により、この質問はより緊急になります。

2009年7月の会議のメモ:

タイプは同じであることが意図されています。

現在、解像度は次の文言を [dcl.fct.spec]/4 に組み込みました。

extern inline関数の本体内で定義された型は、すべての翻訳単位で同じ型です。

(注: 次のリリースでは可能性があります )。ただし、MSVCは上記の言い回しを考慮していません。

したがって、そのような関数の本体内のラムダは安全です。なぜなら、クロージャー型の定義は実際にブロックスコープにあるからです( [expr.prim.lambda]/ )。
したがって、fの複数の定義が明確に定義されました。

ラムダ、特に関数テンプレートを使用できる外部リンケージを持つエンティティがさらに多くあるため、この解決策はすべてのシナリオをカバーするわけではありません-これは別のコアの問題でカバーされるべきです。
それまでの間、Itaniumには 適切なルール が含まれており、より多くの状況でこのようなラムダの型が一致するようになっています。


異なる閉鎖タイプがODR違反である理由を標準化したものが後に続きます。 [basic.def.odr]/6 の箇条書き(6.2)および(6.4)を検討してください。

[...]の定義は複数存在する場合があります。 Dという名前のエンティティが複数の変換単位で定義されている場合、Dの各定義は同じトークンシーケンスで構成されます。そして

(6.2)-Dの各定義で、[basic.lookup]に従って検索された対応する名前は、Dの定義内で定義されたエンティティを参照するか、オーバーロード解決後、同じエンティティを参照する([over.match])および部分テンプレート特化のマッチング後([temp.over])、[…];そして

(6.4)-Dの各定義、参照されるオーバーロードされた演算子、暗黙的な呼び出し変換関数、constructors、演算子の新しい関数と演算子の削除関数、同じ関数、またはDの定義内で定義された関数を参照するものとします; […]

これが効果的に意味するのは、エンティティの定義で呼び出される関数は、すべての翻訳単位で同じであるということです。またはその定義内で定義されています、ローカルクラスとそのメンバー。つまりラムダ自体の使用には問題はありませんが、定義の外で定義されているため、関数テンプレートに渡すことは明らかに問題です。

Cを使用した例では、クロージャタイプはクラス内で定義されます(スコープはスコープを囲む最小のスコープです)。クロージャータイプが2つのTUで異なり、標準がクロージャータイプの一意性を意図せずに暗示している場合、コンストラクターはfunctionのコンストラクターテンプレートの異なる特殊化をインスタンス化して呼び出し、上記の引用の(6.4)に違反します。

39
Columbo

[〜#〜] updated [〜#〜]

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

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

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

8
oliora