次のコードを検討してください。
struct Base
{
int x;
};
struct Bar : Base
{
int y;
};
struct Foo : Base
{
int z;
};
Bar* bar = new Bar;
Foo* foo = new Foo;
Base* returnBase()
{
Base* obj = !bar ? foo : bar;
return obj;
}
int main() {
returnBase();
return 0;
}
これはClangまたはGCCでは機能しません。
エラー:異なるポインタータイプ「Foo *」と「Bar *」間の条件式にキャストBase * obj =!barがありませんか? foo:バー;
つまり、コンパイルするには、コードを次のように変更する必要があります。
Base* obj = !bar ? static_cast<Base*>(foo) : bar;
Base*
への暗黙的なキャストが存在するので、コンパイラーがそうすることを妨げるものは何ですか?
言い換えると、なぜBase* obj = foo;
はキャストなしで機能するのに、?:
演算子を使用しても機能しないのですか? Base
部分を使用することが明確でないためですか?
C++標準ドラフトN4296、セクション5.16からの引用条件演算子、段落6.3:
- 2番目と3番目のオペランドの一方または両方にポインター型があります。 ポインター変換(4.10)および修飾変換(4.4)実行されるは、それらを複合ポインター型にします(5節)。結果は複合ポインター型です。
セクション5Expressions、パラグラフ13.8および13.9:
タイプT1およびT2をそれぞれ持つ2つのオペランドp1およびp2の複合ポインタータイプは、少なくとも1つはメンバータイプまたはstd :: nullptr_tへのポインターまたはポインターです。
- t1とT2が類似したタイプ(4.4)の場合、T1とT2のcv結合タイプ。
- それ以外の場合、複合ポインタ型の決定を必要とするプログラムは不正な形式です。
注:ヒットしないことを示すために、ここに5/13.8をコピーしました。実際に有効なのは5/13.9、「プログラムは不正です」です。
セクション4.10ポインター変換、パラグラフ3:
Dがクラスタイプである「cv Dへのポインター」タイプのprvalueは、「cv Bへのポインター」タイプのprvalueに変換できます。ここで、BはDの基本クラス(10節)です。BがDのアクセス不能(11節)またはあいまい(10.2)の基本クラス。この変換を必要とするプログラムは不正な形式です。変換の結果は、派生クラスオブジェクトの基本クラスサブオブジェクトへのポインターです。 NULLポインター値は、宛先タイプのNULLポインター値に変換されます。
したがって、FooとBarの両方が同じ基本クラスから派生することは(まったく)問題ではありません。 FooへのポインターとBarへのポインターが相互に変換できないことだけが重要です(継承関係はありません)。
条件演算子のベースポインター型への変換を許可するのは良いように聞こえますが、実際には問題があります。
あなたの例では
struct Base {};
struct Foo : Base {};
struct Bar : Base {};
cond ? foo : bar
のタイプをBase*
にするのは明らかな選択のように思えるかもしれません。
しかし、その論理は一般的な場合には耐えられません
例えば。:
struct TheRealBase {};
struct Base : TheRealBase {};
struct Foo : Base {};
struct Bar : Base {};
cond ? foo : bar
はBase*
型ですか、またはTheRealBase*
型ですか?
どうですか:
struct Base1 {};
struct Base2 {};
struct Foo : Base1, Base2 {};
struct Bar : Base1, Base2 {};
cond ? foo : bar
はどのタイプになりますか?
または今はどうですか:
struct Base {};
struct B1 : Base {};
struct B2 : Base {};
struct X {};
struct Foo : B1, X {};
struct Bar : B2, X {};
Base
/ \
/ \
/ \
B1 B2
| X |
| / \ |
|/ \ |
Foo Bar
痛い!タイプcond ? foo : bar
の幸運な推論。私は知っている、lyい、lyい、非実用的であり、狩りにふさわしいが、標準にはまだこのためのルールが必要です。
ポイントを取得します。
また、std::common_type
は条件演算子規則の観点から定義されていることにも留意してください。
言い換えると、なぜ
Base* obj = foo;
はキャストなしで機能するのに、?:
演算子を使用しても機能しないのですか?
条件式のタイプは、割り当てられているものに依存しません。あなたの場合、コンパイラは、割り当てられているものに関係なく、!bar ? foo : bar;
を評価できる必要があります。
あなたの場合、foo
の型に変換されたbar
もbar
もfoo
の型に変換できないため、これは問題です。
それは、ベースパーツを使用することが明確でないためですか?
正確に。
Base*
への暗黙的なキャストが存在するので、コンパイラーがそうすることを妨げるものは何ですか?
[expr.cond]/7によると、
左辺値から右辺値、配列からポインター、および関数からポインターの標準変換は、第2および第3オペランドに対して実行されます。これらの変換後、次のいずれかが保持されます。
- ...
- 2番目と3番目のオペランドの一方または両方にポインター型があります。ポインター変換、関数ポインター変換、および修飾変換を実行して、それらを複合ポインター型にします。結果は複合ポインター型です。
複合ポインター型は[expr.type]/4で定義されています:
それぞれタイプT1およびT2を持つ2つのオペランドp1およびp2の複合ポインタータイプで、少なくとも1つはポインターまたはメンバーへのポインタータイプまたはstd :: nullptr_tです:
p1とp2の両方がNULLポインター定数の場合、std :: nullptr_t;
p1またはp2がNULLポインター定数である場合、それぞれT2またはT1。
t1またはT2が「cv1 voidへのポインター」であり、もう一方の型が「cv2 Tへのポインター」である場合、Tはオブジェクトタイプまたはvoid、「cv12 voidへのポインター」、cv12はcv1とcv2の結合です。
t1またはT2が「noexcept関数へのポインター」で、他のタイプが「関数へのポインター」であり、それ以外の場合は関数タイプが同じである場合、「関数へのポインター」。
t1が「cv1 C1へのポインター」であり、T2が「cv2 C2へのポインター」である場合、C1はC2に参照関連、C2はC1に参照関連、T1とT2のcv結合型、またはcv結合それぞれT2およびT1のタイプ。
t1が「タイプcv1 U1のC1のメンバーへのポインター」であり、T2が「タイプcv2 U2のC2のメンバーへのポインター」である場合、C1はC2に参照関連、またはC2はC1に参照関連T2およびT1またはcv結合型のT1およびT2のそれぞれ。
t1とT2が類似したタイプの場合、T1とT2のcv結合タイプ。
それ以外の場合、複合ポインター型の決定を必要とするプログラムの形式は不適切です。
これで、「共通ベース」へのポインタが複合ポインタ型ではないことがわかります。
言い換えると、なぜ
Base* obj = foo;
はキャストなしで機能するのに、?:
演算子を使用しても機能しないのですか?Base
部分を使用することが明確でないためですか?
問題は、初期化を監視せずに、ルールが条件式のタイプを個別に検出する必要があることです。
具体的には、ルールは、次の2つのステートメントの条件式が同じタイプであると検出する必要があります。
Base* obj = !bar ? foo : bar;
bar ? foo : bar;
ここで、2番目のステートメントの条件式が不正な形式であることに疑いがない場合1、最初のステートメントでそれを整形式にする理由は何ですか?
1 もちろん、そのような表現を整形式にするためのルールを作成できます。たとえば、複合ポインタ型に明確な基本型へのポインタを含めます。ただし、これはこの疑問を超えたものであり、ISO C++委員会で議論する必要があります。