ここでの問題は、C ++ 03標準のバグ/誤動作/穴であり、さまざまなコンパイラがさまざまな方法で問題にパッチを適用しようとしています。(この問題はC ++ 11標準には存在しなくなりました。)
両方の標準のセクション8.5.3/5は、参照の初期化方法を指定しています。これがC++03バージョンです(リストの番号付けは私のものです):
タイプへの参照は、次のようcv1 T1
にタイプの式によって初期化さcv2 T2
れます。
初期化式の場合
- は左辺値(ただしビットフィールドではありません)であり、「cv1T1」は「cv2T2」と参照互換性があります。
- クラスタイプ(つまり、
T2
クラスタイプ)を持ち、タイプの左辺値に暗黙的に変換できますcv3 T3
。ここで、cv1 T1
は参照互換です。cv3 T3
次に、最初のケースでは参照が初期化子式の左辺値に直接バインドされ、2番目のケースでは参照が変換の左辺値の結果にバインドされます。
それ以外の場合、参照は不揮発性constタイプになります(つまり、にcv1
なりますconst
)。
イニシャライザ式がT2
クラスタイプの右辺値であり、とのcv1 T1
参照互換性がcv2 T2
ある場合、参照は次のいずれかの方法でバインドされます(選択は実装定義です)。
- 参照は、右辺値(3.10を参照)で表されるオブジェクトまたはそのオブジェクト内のサブオブジェクトにバインドされます。
- タイプ
cv1 T2
[sic]の一時オブジェクトが作成され、コンストラクターが呼び出されて、右辺値オブジェクト全体が一時オブジェクトにコピーされます。参照は、一時または一時内のサブオブジェクトにバインドされます。
コピーを作成するために使用されるコンストラクターは、コピーが実際に実行されるかどうかに関係なく呼び出し可能でなければなりません。
cv1 T1
それ以外の場合は、非参照コピー初期化(8.5)のルールを使用して、初期化子式からタイプの一時オブジェクトが作成および初期化されます。次に、参照は一時的なものにバインドされます。
手元の質問に関係する3つのタイプがあります:
- 作成する参照のタイプ。標準(両方のバージョン)は、このタイプをとして示し
T1
ます。この場合はですstruct A
。
- 初期化式のタイプ。規格では、このタイプをとして示してい
T2
ます。この場合、初期化式は変数c
であり、 。も同様T2
ですstruct C
。との参照互換性がstruct A
ないため、参照をに直接バインドすることはできないことに注意してください。中間体が必要です。struct C
c
- 中間体のタイプ。規格では、このタイプをとして示してい
T3
ます。この場合、これはstruct B
です。に変換演算子C::operator B()
を適用c
すると、左辺値c
が右辺値に変換されることに注意してください。
struct A
は参照と互換性がないため、1.1および3とラベル付けしたものによる初期化は終了していstruct C
ます。変換演算子C::operator B()
を使用する必要があります。1.2がアウトですこの変換演算子は右辺値を返すため、これは1.2を除外します。残っているのはオプション4だけで、タイプの一時を作成しますcv1 T1
。2003バージョンの標準に厳密に準拠すると、1つだけで十分ですが、この問題に対して2つの一時的なものを作成する必要があります。
標準の2011バージョンは、オプション3を次のように置き換えることで問題を修正します。
gccファミリーのコンパイラーはインテントよりも厳密なコンプライアンスを選択したようです(不要な一時的なものを作成しないようにします)が、「b」を出力する他のコンパイラーはインテント/標準への修正を選択しました。厳格なコンプライアンスを選択することは必ずしも称賛に値するわけではありません。2003バージョンの標準(たとえば)には、他にもバグや機能の誤りがstd::set
あり、gccファミリは厳密なコンプライアンスよりも健全性を選択しました。