为了账号安全,请及时绑定邮箱和手机立即绑定

`is_base_of`是如何工作的?

/ 猿问

`is_base_of`是如何工作的?

C++
MM们 2019-08-24 18:09:50

`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 sampleclass 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。这适用于各种编译器。


查看完整描述

3 回答

?
侃侃无极

如果它们是相关的

让我们暂时假设它B实际上是一个基础D。然后,对于调用check,两个版本都是可行的,因为Host可以转换为D*  B*。它是用户定义的转换序列,分别由13.3.3.1.2from 和Host<B, D>to 描述。为了找到可以转换类的转换函数,根据第一函数合成以下候选函数D*B*check13.3.1.5/1

D* (Host<B, D>&)

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

对于第二个功能,存在以下候选者:

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

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

如果你要删除 const,我们会有以下候选人

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

这意味着我们不能再通过constness选择了。在普通的重载解析方案中,调用现在是不明确的,因为通常返回类型不会参与重载解析。但是,对于转换函数,有一个后门。如果两个转换函数同样好,那么它们的返回类型决定谁是最好的13.3.3/1。因此,如果你想删除的常量,那么第一个将采取,因为B*转换更好的B*D*B*

现在用户定义的转换顺序更好?第二次或第一次检查功能?规则是用户定义的转换序列只有在根据它们使用相同的转换函数或构造函数时才能进行比较13.3.3.2/3b2。这就是这里的情况:两者都使用第二个转换函数。请注意,因此const很重要,因为它强制编译器采用第二个转换函数。

既然我们可以比较它们 - 哪一个更好?规则是从转换函数的返回类型到目标类型的更好转换(再次13.3.3.2/3b2)。在这种情况下,D*转换为to to D*than B*。因此选择了第一个函数,我们认识到了继承!

请注意,由于我们从未需要实际转换为基类,因此我们可以识别私有继承,因为我们是否可以从a转换D*为a B*不依赖于继承的形式根据4.10/3

如果他们没有关系

现在让我们假设它们与继承无关。因此,对于第一个功能,我们有以下候选人

D* (Host<B, D>&)

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

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

由于我们无法转换D*B*如果我们没有继承关系,我们现在在两个用户定义的转换序列中没有共同的转换函数!因此,如果不是第一个函数是模板的事实,我们将是模棱两可的。当有一个同样好的非模板函数时,模板是第二选择13.3.3/1。因此,我们选择非模板函数(第二个),我们认识到B和之间没有继承D


查看完整回答
反对 回复 2019-08-24
?
动漫人物

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

sizeof(check(Host<B,D>(), int()))零件开始。编译器可以很快看到这check(...)是一个函数调用表达式,因此需要对其进行重载解析check。有两个候选重载可用,template <typename T> yes check(D*, T);no check(B*, int);。如果第一个被选中,你会得到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通过继承相关,则转换序列Host<B,D>::operator D*()D*->B*存在。现在假设D确实从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不从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)总会被选中。


查看完整回答
反对 回复 2019-08-24
?
叮当猫咪

在你的第二个问题之后,请注意,如果它不是const,如果用B == D实例化,Host将是格式错误的。但is_base_of的设计使得每个类都是它自己的基础,因此转换运算符之一必须是const。


查看完整回答
反对 回复 2019-08-24

添加回答

回复

举报

0/150
提交
取消
意见反馈 邀请有奖 帮助中心 APP下载
官方微信