「is_base_of」はどのように機能しますか?


118

次のコードはどのように機能しますか?

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];
  1. B民間ベースです。これはどのように作動しますか?

  2. これoperator B*()はconstです。どうしてそれが重要ですか?

  3. なぜtemplate<typename T> static yes check(D*, T);より良いですstatic yes check(B*, int);か?

:の縮小版(マクロは削除されています)ですboost::is_base_of。そして、これは幅広いコンパイラで動作します。


4
テンプレートパラメータと実際のクラス名に同じ識別子を使用することは非常に混乱します...
Matthieu M.

1
@Matthieu M.、私は自分で修正することにしました:)
Kirill V. Lyadvinsky

2
いくつかの時間前、私はの代替実装を書いたis_base_ofideone.com/T0C1Vを(GCC4.3は罰金を動作します)けれどもそれは古いGCCのバージョンでは動作しません。
Johannes Schaub-litb

3
よし、散歩するよ。
jokoon

2
この実装は正しくありません。is_base_of<Base,Base>::valueする必要がありtrueます。これは戻りますfalse
chengiz 2016年

回答:


109

それらが関連している場合

それBが実際にのベースであると仮定してみましょうD。次に、を呼び出すとcheckHostとに変換できるため、両方のバージョンが実行可能にD* なり B*ます。13.3.3.1.2fromとHost<B, D>to D*B*それぞれ説明されているユーザー定義の変換シーケンスです。クラスを変換できる変換関数を見つけるために、以下の候補関数が最初のcheck関数に対して次のように合成されます。13.3.1.5/1

D* (Host<B, D>&)

に変換B*できないため、最初の変換関数は候補ではありませんD*

2番目の機能には、以下の候補が存在します。

B* (Host<B, D> const&)
D* (Host<B, D>&)

これらは、ホストオブジェクトを取得する2つの変換関数候補です。1つ目はconst参照で取得し、2つ目は取得しません。したがって、2番目は非const *thisオブジェクト(暗黙のオブジェクト引数)に一致し、2番目の関数13.3.3.2/3b1sb4への変換に使用されます。B*check

const を削除すると、次の候補が得られます

B* (Host<B, D>&)
D* (Host<B, D>&)

これは、私たちがもはや定数で選択することができないことを意味します。通常のオーバーロード解決シナリオでは、通常、戻り値の型はオーバーロード解決に参加しないため、呼び出しはあいまいになります。ただし、変換関数にはバックドアがあります。2つの変換関数が同等に優れている場合、それらの戻り値の型によって、に応じて最適な変換関数が決まり13.3.3/1ます。あなたはCONSTを削除するかどうのでこのように、最初は、取られるだろうB*に変換し、より良いB*よりD*B*

次に、ユーザー定義の変換シーケンスはどれが優れていますか?2番目または1番目のチェック機能用の1つですか?ルールによると、ユーザー定義の変換シーケンスは、に従って同じ変換関数またはコンストラクターを使用する場合にのみ比較でき13.3.3.2/3b2ます。これはまさにここに当てはまります。どちらも2番目の変換関数を使用します。したがって、constはコンパイラに2番目の変換関数を強制するため、constが重要であることに注意してください。

それらを比較できるので、どちらが良いですか?ルールは、変換関数の戻り値の型から宛先の型へのより良い変換が勝つ(再び13.3.3.2/3b2)ことです。この場合、はにD*変換D*B*ます。したがって、最初の関数が選択され、継承が認識されます。

基本クラスに実際に変換する必要はなかったので、aからa に変換できるかどうかは継承の形式に依存しないため、プライベート継承を認識できますD*B*4.10/3

それらが関連していない場合

次に、それらが継承によって関連付けられていないと仮定しましょう。したがって、最初の関数には次の候補があります。

D* (Host<B, D>&) 

もう1つは、別のセットです。

B* (Host<B, D> const&)

我々は変換できませんのでD*B*私たちが継承関係を持っていない場合、我々は今、2つのユーザー定義の変換シーケンスの間には、共通の変換機能を持っていません!したがって、最初の関数がテンプレートであるという事実がなければ、あいまいになります。テンプレートは、に従って同等に良い非テンプレート関数がある場合の2番目の選択肢13.3.3/1です。したがって、非テンプレート関数(2番目の関数)を選択し、BD!の間に継承がないことを認識しています。


2
ああ!アンドレアスは段落が正しかったが、残念ながら彼はそのような答えを出さなかった:)お時間をいただき、ありがとうございます。
Matthieu M. 2010

2
これが私のお気に入りの答えになります...質問:C ++標準全体を読んだか、C ++委員会で働いているだけですか?おめでとう!
Marco A. 14

4
C ++コミットで動作する@DavidKerninは、C ++がどのように動作するかを自動的に知らせません:)したがって、詳細を知るために必要な標準の部分を必ず読む必要があります。それをすべて読んでいないので、ほとんどの標準ライブラリやスレッド関連の質問にはお答えできません:)
Johannes Schaub-litb

1
@underscore_d公平を期すために、仕様ではstd ::の特性を禁止していないため、コンパイラの魔法を使用して標準ライブラリの実装者が好きなように使用することができます。彼らはまた、コンパイル時間とメモリ使用量をスピードアップするのに役立つテンプレートの曲芸を避けます。これは、インターフェイスがのようになっていても当てはまりますstd::is_base_of<...>。それはすべて内部です。
ヨハネスシャウブ-litb 2016

2
もちろん、一般的なライブラリboost::では、使用する前にこれらの組み込み関数が使用可能であることを確認する必要があります。そして私は、コンパイラーの助けなしに物事を実装するために、彼らの間にある種の「挑戦的に受け入れられた」考え方があると感じています:)
Johannes Schaub-litb

24

手順を見て、それがどのように機能するかを考えてみましょう。

sizeof(check(Host<B,D>(), int()))パーツから始めます。コンパイラcheck(...)は、これが関数呼び出し式であることをすばやく確認できるため、でオーバーロード解決を行う必要がありますcheck。そこ2つの候補のオーバーロードが用意されています、template <typename T> yes check(D*, T);no check(B*, int);。最初に選択されている場合は、あなたが得る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* constconstである必要があるのか​​もわかります。それ以外の場合は、Host<B,D> -> Host<B,D> constステップの必要がなく、あいまいさがなく、no check(B*, int)常に選択されます。


あなたの説明はの存在を説明していませんconst。あなたの答えが真なら、それconstは必要ありません。しかし、それは真実ではありません。削除constしてトリックは機能しません。
Alexey Malistov

constがなければ、2つの変換シーケンスno check(B*, int)があいまいではなくなります。
MSalters

のみを残す場合no check(B*, int)、関連するBおよびについてはD、あいまいではありません。operator D*()constがないため、コンパイラは変換を実行することを明確に選択します。それは少し反対の方向にあります:const を削除すると、曖昧さを感じることになりoperator B*()ますが、これは、B*like へのポインター変換を必要としない優れた戻り型を提供するという事実によって解決されますD*
Johannes Schaub-litb

それがまさにポイントです。一時的なものB*からを取得するための2つの異なる変換シーケンスのあいまいさです<Host<B,D>()
MSalters 2010年

これはより良い答えです。ありがとう!それで、私が理解したように、ある機能がより良いが曖昧な場合、別の機能が選択されますか?
user1289 2015

4

アクセシビリティチェックの前に過負荷の解決が行わ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プライベートベースであるという事実は、チェックの実行を妨げません。変換を妨げるだけですが、実際の変換は要求しません;)


ちょっと。ベース変換は一切行われません。評価hostされていD*ないB*式に、または評価されていない式に任意に変換されます。何らかの理由で、特定の条件下でよりD*も望ましいB*です。
Potatoswatter、

答えは13.3.1.1.2にあると思いますが、詳細についてはまだ整理していません:)
Andreas Brinck

私の答えは「なぜ私的な​​作品なのか」の部分だけを説明していますが、場合によっては完全な解決プロセスの明確な説明を熱心に待っていますが、sellibitzeの答えは確かにより完全です。
Matthieu M. 2010

2

おそらく、オーバーロードの解決に関する部分的な順序付けと関係があります。DがBから派生している場合、D *はB *よりも専門的です。

正確な詳細はかなり複雑です。さまざまなオーバーロード解決ルールの優先順位を把握する必要があります。部分的な順序付けは1つです。変換シーケンスの長さ/種類は別のものです。最後に、2つの実行可能な関数が同等に優れていると見なされた場合、関数テンプレートよりも非テンプレートが選択されます。

これらのルールがどのように相互作用するかを調べる必要はありませんでした。しかし、部分的な順序付けが他のオーバーロード解決ルールを支配しているようです。DがBから派生していない場合、部分的な順序付け規則は適用されず、非テンプレートの方が魅力的です。DがBから派生すると、部分的な順序付けが始まり、関数テンプレートがより魅力的なものになります(見たところ)。

継承が特権的である場合については、コードはパブリック継承を必要とするD *からB *への変換を要求しません。


それはそのようなものだと思いますis_base_of。これを確実にするために貢献者が実装したループとループについて、boostアーカイブに関する広範な議論を見たのを覚えています。
Matthieu M. 2010

The exact details are rather complicated-それがポイントです。説明してください。知りたいです。
Alexey Malistov

@Alexey:まあ、私はあなたを正しい方向に向けたと思った。この場合、さまざまなオーバーロード解決ルールがどのように相互作用するかを確認してください。この過負荷のケースの解決に関して、Bから派生するDとBから派生しないDの唯一の違いは、部分的な順序付けルールです。オーバーロードの解決は、C ++標準の§13で説明されています。下書きは無料で入手できます:open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1804.pdf
10

過負荷の解決は、そのドラフトで16ページに及びます。この場合のルールとルール間の相互作用を本当に理解する必要がある場合は、§13.3のセクション全体を読む必要があると思います。私はここで100%正しい、あなたの基準に合った答えを得ることは期待していません。
10

興味がありましたら、私の説明で説明してください。
Johannes Schaub-litb

0

2番目の質問に続いて、constでない場合、B == Dでインスタンス化された場合、Hostは不正な形式になることに注意してください。 constである。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.