`is_base_of`是如何工作的?

时间:2010-05-26 07:43:29

标签: c++ templates overloading implicit-conversion typetraits

以下代码如何运作?

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);

  4. 注意:它是boost::is_base_of的缩减版本(已删除宏)。这适用于各种编译器。

5 个答案:

答案 0 :(得分:107)

如果他们是相关的

让我们暂时假设B实际上是D的基础。然后,对于check的调用,两个版本都可行,因为Host可以转换为D* B*。它是13.3.3.1.2分别从Host<B, D>D*B*所描述的用户定义的转化序列。为了找到可以转换类的转换函数,根据check

为第一个13.3.1.5/1函数合成以下候选函数
D* (Host<B, D>&)

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

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

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

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

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

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

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

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

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

请注意,由于我们从未需要实际转换为基类,因此我们可以识别私有继承,因为我们是否可以从D*转换为根据{{​​1}}

B*不依赖于继承的形式

如果它们不相关

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

4.10/3

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

D* (Host<B, D>&) 

如果我们没有继承关系,我们就无法将B* (Host<B, D> const&) 转换为D*,我们现在在两个用户定义的转换序列中没有共同的转换函数!因此,如果不是第一个函数是模板的事实,我们将模糊。当根据B*存在同样优秀的非模板函数时,模板是第二选择。因此,我们选择非模板函数(第二个),我们认识到13.3.3/1B之间没有继承!

答案 1 :(得分: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)

答案 2 :(得分: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是私人基础的事实不会阻止检查发生,它只会阻止转换,但我们从不要求实际转换;)

答案 3 :(得分:2)

这可能与部分订购w.r.t有关。超载分辨率。在D来自B的情况下,D *比B *更专业。

具体细节相当复杂。您必须弄清楚各种重载决策规则的优先级。部分订购是一个。转换序列的长度/种类是另一种。最后,如果两个可行的函数被认为同样好,则选择非模板而不是函数模板。

我从来不需要查看这些规则如何相互作用。但似乎偏序排序主导其他重载决策规则。当D不是从B派生时,部分排序规则不适用,非模板更具吸引力。当D派生自B时,部分排序开始并使函数模板更具吸引力 - 看起来像。

至于继承是privete:代码永远不会要求从D *到B *的转换,这需要公共继承。

答案 4 :(得分:0)

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