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*()是常量。它为什么如此重要?

  3. 为什么template<typename T> static yes check(D*, T);优于static yes check(B*, int);

注意:它是boost::is_base_of. 这适用于广泛的编译器。

4

5 回答 5

112

如果他们是相关的

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

D* (Host<B, D>&)

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

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

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

这些是采用宿主对象的两个转换函数候选者。第一个通过 const 引用获取它,而第二个没有。*this因此,第二个是非常量对象(隐含的对象参数)的更好匹配,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*to更好B*

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

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

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

如果他们不相关

现在让我们假设它们没有继承关系。因此,对于第一个函数,我们有以下候选者

D* (Host<B, D>&) 

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

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

由于如果我们没有继承关系就无法转换D*为,因此我们现在在两个用户定义的转换序列之间没有通用的转换函数!因此,如果不是因为第一个函数是模板这一事实B*,我们就会模棱两可。当有一个根据13.3.3/1. 因此,我们选择了非模板函数(第二个),并且我们认识到Band之间没有继承D

于 2010-05-26T14:31:09.243 回答
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)总是会被选择。

于 2010-05-26T11:35:06.307 回答
5

由于重载决议发生在可访问性检查之前,该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私有基地这一事实不会阻止检查的发生,它只会阻止转换,但我们从不要求实际转换;)

于 2010-05-26T08:19:19.543 回答
2

它可能与部分排序 wrt 重载决议有关。在 D 派生自 B 的情况下,D* 比 B* 更专业。

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

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

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

于 2010-05-26T08:10:29.830 回答
0

在您的第二个问题之后,请注意,如果它不是用于 const,则如果用 B == D 实例化 Host 将是不正确的。但是 is_base_of 的设计使得每个类都是其自身的基础,因此必须转换运算符之一是常数。

于 2014-03-10T13:36:15.093 回答