次のコードはどのように機能しますか?
_typedef char (&yes)[1];
typedef char (&no)[2];
template <typename B, typename D>
struct Host
{
operator B*() const;
operator D*();
};
template <typename B, typename D>
struct is_base_of
{
template <typename T>
static yes check(D*, T);
static no check(B*, int);
static const bool value = sizeof(check(Host<B,D>(), int())) == sizeof(yes);
};
//Test sample
class Base {};
class Derived : private Base {};
//Expression is true.
int test[is_base_of<Base,Derived>::value && !is_base_of<Derived,Base>::value];
_
B
はプライベートベースであることに注意してください。これはどのように作動しますか?
operator B*()
はconstであることに注意してください。どうしてそれが重要ですか?
template<typename T> static yes check(D*, T);
がstatic yes check(B*, int);
よりも優れているのはなぜですか?
注:_boost::is_base_of
_の縮小版(マクロは削除されます)。そして、これは広範囲のコンパイラで動作します。
B
が実際にD
のベースであると仮定しましょう。 check
の呼び出しでは、Host
を_D*
_and_B*
_に変換できるため、両方のバージョンが実行可能です。 。それは、それぞれ_13.3.3.1.2
_から_Host<B, D>
_から_D*
_および_B*
_に記述されるユーザー定義の変換シーケンスです。クラスを変換できる変換関数を見つけるために、次の候補関数が_13.3.1.5/1
_に従って最初のcheck
関数に対して合成されます
_D* (Host<B, D>&)
_
_B*
_は_D*
_に変換できないため、最初の変換関数は候補ではありません。
2番目の機能には、次の候補があります。
_B* (Host<B, D> const&)
D* (Host<B, D>&)
_
これらは、Hostオブジェクトを取得する2つの変換関数候補です。最初はconst参照によって取得し、2番目は取得しません。したがって、2番目は、_*this
_による非定数_13.3.3.2/3b1sb4
_オブジェクト(暗黙オブジェクト引数)によりよく一致し、_B*
_ 2番目のcheck
関数。
remove constの場合、次の候補があります。
_B* (Host<B, D>&)
D* (Host<B, D>&)
_
これは、もはやconstnessで選択できないことを意味します。通常のオーバーロード解決シナリオでは、通常、戻り値の型はオーバーロード解決に関与しないため、呼び出しはあいまいになります。ただし、変換関数にはバックドアがあります。 2つの変換関数が同等に優れている場合、それらの戻り値の型によって、_13.3.3/1
_に従って誰が最適かが決まります。したがって、constを削除する場合は、_B*
_が_B*
_を_D*
_に変換するよりも_B*
_の方が_13.3.3.2/3b2
_に変換されるため、最初のものが使用されます。
今、ユーザー定義の変換シーケンスは何ですか? 2番目または1番目のチェック機能用ですか?ルールでは、ユーザー定義の変換シーケンスは、_13.3.3.2/3b2
_に従って同じ変換関数またはコンストラクターを使用する場合にのみ比較できます。これはまさにこの場合です:両方とも2番目の変換関数を使用します。 constが重要であることに注意してください。これは、コンパイラに2番目の変換関数を強制的に実行させるためです。
それらを比較できるので、どちらが良いですか?ルールは、変換関数の戻り値の型から変換先の型へのより適切な変換が優先されることです(これも_D*
_による)。この場合、_D*
_は_B*
_よりも_D*
_に変換されます。したがって、最初の関数が選択され、継承が認識されます!
基本クラスに実際に変換する必要がないため、_SOMECODE)から変換できるかどうかによって---(private inheritanceを認識できることに注意してください。 _B*
_への__は、_4.10/3
_による継承の形式に依存しません
次に、それらが継承によって関連していないと仮定しましょう。したがって、最初の関数には次の候補があります
_D* (Host<B, D>&)
_
そして2つ目は、別のセットがあります
_B* (Host<B, D> const&)
_
継承関係がない場合は_D*
_を_B*
_に変換できないため、2つのユーザー定義の変換シーケンスに共通の変換関数はありません!したがって、最初の関数がテンプレートであるという事実がない場合、ambiguousになります。 _13.3.3/1
_に従って同等に優れた非テンプレート関数がある場合、テンプレートは2番目の選択肢です。したがって、非テンプレート関数(2番目の関数)を選択すると、B
とD
!の間に継承がないことがわかります。
手順を見て、それがどのように機能するかを見てみましょう。
sizeof(check(Host<B,D>(), int()))
部分から始めます。コンパイラは、このcheck(...)
が関数呼び出し式であることをすぐに確認できるため、check
でオーバーロード解決を行う必要があります。 template <typename T> yes check(D*, T);
とno check(B*, int);
の2つの候補オーバーロードが利用可能です。最初のものが選択された場合、sizeof(yes)
が得られ、そうでない場合はsizeof(no)
が得られます。
次に、オーバーロード解決を見てみましょう。最初のオーバーロードはテンプレートのインスタンス化check<int> (D*, T=int)
で、2番目の候補はcheck(B*, int)
です。提供される実際の引数は、Host<B,D>
およびint()
です。 2番目のパラメーターは明らかにそれらを区別しません。最初のオーバーロードをテンプレートのオーバーロードにするだけでした。テンプレートパーツが関連する理由は後で説明します。
次に、必要な変換シーケンスを見てください。最初のオーバーロードについては、Host<B,D>::operator D*
-ユーザー定義の変換が1つあります。第二に、オーバーロードはトリッキーです。 B *が必要ですが、おそらく2つの変換シーケンスがあります。 1つはHost<B,D>::operator B*() const
経由です。 BとDが継承によって関連付けられている場合(のみ)、変換シーケンスHost<B,D>::operator D*()
+ D*->B*
が存在します。 Dが実際にBから継承すると仮定します。2つの変換シーケンスはHost<B,D> -> Host<B,D> const -> operator B* const -> B*
とHost<B,D> -> operator D* -> D* -> B*
です。
したがって、関連するBとDの場合、no check(<Host<B,D>(), int())
はあいまいになります。その結果、テンプレート化されたyes check<int>(D*, int)
が選択されます。ただし、DがBから継承しない場合、no check(<Host<B,D>(), int())
はあいまいではありません。この時点で、最短の変換シーケンスに基づいて過負荷を解決することはできません。ただし、変換シーケンスが等しい場合、オーバーロード解決はテンプレート以外の関数、つまりno check(B*, int)
を優先します。
これで、継承がプライベートであることが重要である理由がわかりました。この関係は、アクセスチェックが発生する前に、no check(Host<B,D>(), int())
をオーバーロード解決から削除するだけです。また、operator B* const
がconstでなければならない理由もわかります。それ以外の場合、Host<B,D> -> Host<B,D> const
ステップは不要で、あいまいさはなく、no check(B*, int)
が常に選択されます。
private
ビットは、is_base_of
によって完全に無視されます。これは、アクセシビリティチェックの前にオーバーロード解決が行われるためです。
これは簡単に確認できます:
class Foo
{
public:
void bar(int);
private:
void bar(double);
};
int main(int argc, char* argv[])
{
Foo foo;
double d = 0.3;
foo.bar(d); // Compiler error, cannot access private member function
}
ここでも同じことが当てはまります。B
がプライベートベースであるという事実は、チェックの実行を妨げるものではなく、変換を妨げるだけですが、実際の変換を要求することはありません;)
それはおそらく部分順序w.r.tに関係しています。オーバーロード解決。 DがBから派生する場合、D *はB *よりも特殊です。
正確な詳細はかなり複雑です。さまざまなオーバーロード解決規則の優先順位を把握する必要があります。半順序は1つです。変換シーケンスの長さ/種類は別のものです。最後に、2つの実行可能な関数が同等に優れていると見なされる場合、関数テンプレートよりも非テンプレートが選択されます。
これらのルールの相互作用を調べる必要はありませんでした。ただし、他のオーバーロード解決ルールは部分順序付けが支配的です。 DがBから派生しない場合、部分順序付け規則は適用されず、非テンプレートがより魅力的です。 DがBから派生すると、部分的な順序付けが開始され、関数テンプレートがより魅力的になります。
継承がプライベートであることに関して:コードは、パブリック継承を必要とするD *からB *への変換を要求しません。
2番目の質問に続いて、constでない場合、B == Dでインスタンス化された場合、Hostは不正な形式になることに注意してください。一定である.