“is_base_of”如何工作?

以下代码如何工作?

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简化版本(macros被删除)。 这在广泛的编译器上工作。

如果他们是相关的

让我们暂时假设B实际上是D的基础。 然后为了check呼叫,两个版本都是可行的,因为Host可以转换为D* B* 。 它是用户定义的转换序列,如13.3.3.1.2所描述的,分别从Host<B, D>D*B* 。 为了find可以转换类别的转换函数,根据13.3.1.5/1为第一个check函数合成以下候选函数

 D* (Host<B, D>&) 

第一个转换函数不是候选者,因为B*不能转换为D*

对于第二个函数,存在以下候选项:

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

这些是作为宿主对象的两个转换函数候选者。 第一个是通过const引用,第二个不是。 因此,第二个是13.3.3.2/3b1sb4中非const *this对象( 隐含的对象参数 )的更好匹配,并被用于将第二个check函数转换为B*

如果你想删除 const,我们将有以下的候选人

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

这意味着我们不能再select常量。 在一个普通的重载解决scheme中,调用现在是不明确的,因为通常返回types不会参与重载parsing。 但是,对于转换function,还有一个后门。 如果两个转换函数同样好,那么它们的返回types根据13.3.3/1决定谁是最好的。 因此,如果你删除const,那么第一个将被采取,因为B*更好地转换为B*不是D*B*

现在用户定义的转换序列是更好的? 第二个或第一个检查function? 规则是用户定义的转换序列只能在13.3.3.2/3b2使用相同的转换函数或构造函数进行13.3.3.2/3b2 。 这正是这种情况:都使用第二个转换函数。 注意const的重要性在于它强制编译器执行第二个转换函数。

既然我们可以比较一下 – 哪一个更好? 规则是从转换函数的返回types到目标types的更好的转换获胜(再次由13.3.3.2/3b2 )。 在这种情况下, D*更好地转换成D*不是B* 。 因此,第一个function被选中,我们承认inheritance!

请注意,由于我们从不需要实际转换为基类,因此我们可以识别私有inheritance,因为我们是否可以从D*转换为B*不依赖于4.10/3的inheritanceforms

如果他们不相关

现在让我们假设他们不是通过inheritance关系。 因此,对于第一个function,我们有以下候选人

 D* (Host<B, D>&) 

第二个,我们现在有另一套

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

因为如果我们没有inheritance关系,我们不能把D*转换成B* ,所以我们现在在两个用户定义的转换序列之间没有共同的转换函数! 因此,如果不是第一个函数是一个模板的话,我们就不清楚了。 根据13.3.3/1 ,当非模板函数同样好时,模板是第二select。 因此,我们select非模板函数(第二个),我们认识到BD之间没有inheritance关系。

让我们通过查看步骤来了解它是如何工作的。

sizeof(check(Host<B,D>(), int()))开始sizeof(check(Host<B,D>(), int()))部分。 有两个候选重载可用, template <typename T> yes check(D*, T);no check(B*, int); 。 如果第一个select,你会得到sizeof(yes) ,否则sizeof(no)

接下来,我们来看一下重载分辨率。 第一个重载是一个模板实例check<int> (D*, T=int) ,第二个候选是check(B*, int) 。 提供的实际参数是Host<B,D>int() 。 第二个参数显然不能区分它们; 它只是使第一个重载模板一个。 我们稍后会看到为什么模板部分是相关的。

现在看看所需的转换序列。 对于第一个重载,我们有Host<B,D>::operator D* – 一个用户定义的转换。 第二,过载是棘手的。 我们需要一个B *,但是可能有两个转换序列。 一个是通过Host<B,D>::operator B*() const 。 如果(且仅当)B和D通过inheritance关联,则存在转换序列Host<B,D>::operator D*() + D*->B* 。 现在假设D确实inheritance了B.两个转换序列是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不从Binheritance,那么no check(<Host<B,D>(), int())是不明确的。 此时,重载分辨率不能以最短的转换序列发生。 然而,给定相同的转换序列,重载决议更喜欢非模板函数,即no check(B*, int)

您现在可以看到为什么inheritance是私有的并不重要:在访问检查发生之前,该关系仅用于消除重载parsing中的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完全忽略,因为在可访问性检查之前会发生重载parsing。

你可以简单地validation这一点:

 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是一个私人基地的事实并不妨碍检查的发生,它只会阻止转换,但我们从来没有要求实际的转换;)

这可能与部分重载分辨率的sorting有关。 在D从B派生的情况下,D *比B *更专门化。

确切的细节相当复杂。 你必须找出各种重载parsing规则的优先顺序。 部分sorting是一个。 长度/种类的转换序列是另一种。 最后,如果两个可行的function被认为是同样好的,那么在function模板上select非模板。

我从来不需要查看这些规则如何相互作用。 但似乎偏序排列是支配其他重载parsing规则。 当D不从B派生时,偏序排列规则不适用,非模板更具吸引力。 当D从B派生时,部分sorting就开始了,使得函数模板更具吸引力 – 就像看起来一样。

至于inheritance是privete:代码从来没有要求从D *转换到B *,这将需要公共inheritance。

在第二个问题之后,请注意,如果不是const,那么如果使用B == D实例化,Host将是非法的。但is_base_of被devise为每个类都是它自己的一个基础,因此转换操作符之一必须是const。