“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];
-
请注意,
B
是私人基地。 这个怎么用? -
注意
operator B*()
是const。 它为什么如此重要? -
为什么
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非模板函数(第二个),我们认识到B
和D
之间没有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。