以下代码如何工作?

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. 。这在广泛的编译器上起作用。

有帮助吗?

解决方案

如果他们有关系

让我们暂时假设 B 实际上是 D. 。然后打电话给 check, ,这两个版本都是可行的,因为 Host 可以转换为 D* B*. 。这是用户定义的转换序列,如 13.3.3.1.2Host<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引用将其采用,第二个则没有。因此,第二个是对非const的更好匹配 *this 对象(the 隐含对象参数) 经过 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*B* 不依赖于继承的形式 4.10/3

如果他们没有关系

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

D* (Host<B, D>&) 

第二秒,我们现在还有另一组

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

因为我们不能转换 D*B* 如果我们没有继承关系,那么我们现在在两个用户定义的转换序列之间没有共同的转换功能!因此,我们会 模糊的 如果不是因为第一个函数是模板。当存在非转录功能时,模板是第二选择 13.3.3/1. 。因此,我们选择非模板函数(第二个),我们认识到之间没有继承 BD!

其他提示

让我们通过查看步骤来弄清楚它的工作原理。

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继承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()) 不含糊。在这一点上,超载分辨率不能以最短的转换序列进行ba。但是,给定相等的转换序列,超负荷分辨率更喜欢非网板功能,即 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) 总是会选择。

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 是私人基础不会阻止检查发生,这只会阻止转换,但我们永远不会要求进行实际的转换;)

它可能与部分订购WRT超载分辨率有关。在d源自B的情况下,D*比B*更专业。

确切的细节相当复杂。您必须找出各种超载分辨率规则的先例。部分订购是一个。长度/类型的转换序列是另一个。最后,如果两个可行的函数被认为同样好,则选择非模板而不是函数模板。

我从来不需要查找这些规则的相互作用。但是似乎部分订购在其他过载分辨率规则中占主导地位。当D不从B派生时,部分排序规则将不适用,并且非模板更具吸引力。当D衍生出B时,部分订购会启动并使功能模板更具吸引力 - 看起来似乎。

至于继承是私密的:代码永远不会要求从d*到b*的转换,这需要公开继承。

在您的第二个问题之后,请注意,如果不适合const,则如果使用b == D进行实例化,则主机将不正确,但是设计的IS_BASE_OF的设计使每个类都是本身的基础,因此,转换操作员必须之一成为const。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top