How does `is_base_of` work?

How does the following code work?

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. Note that B is private base. How does this work?

  2. Note that operator B*() is const. Why is it important?

  3. Why is template<typename T> static yes check(D*, T); better than static yes check(B*, int); ?

Note: It is reduced version (macros are removed) of boost::is_base_of. And this works on wide range of compilers.

27920 次浏览

这可能和 WRT 超载分辨率的偏序有关。D * 比 B * 更加专门化,如果 D 来自 B。

确切的细节相当复杂。您必须计算出各种重载解析规则的优先级。部分顺序是一个。转换序列的长度/种类是另一种。最后,如果两个可行的函数被认为是同样好的,非模板被选择在函数模板之上。

我从来不需要查看这些规则是如何相互作用的。但似乎部分排序主导了其他重载解析规则。当 D 不从 B 派生时,部分排序规则不适用,而非模板更有吸引力。当 D 从 B 派生时,部分排序就会起作用,使函数模板更具吸引力——看起来是这样的。

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

is_base_of完全忽略 private位,因为重载解析发生在可访问性检查之前。

你可以简单地证实这一点:

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
}

The same applies here, the fact that B is a private base does not prevent the check from taking place, it would only prevent the conversion, but we never ask for the actual conversion ;)

Let's work out how it works by looking at the steps.

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*-one 用户定义的转换。第二,过载更加棘手。我们需要一个 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)将始终被选中。

If they are related

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

D* (Host<B, D>&)

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

第二个职能的候选人如下:

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

这两个转换函数候选对象采用主机对象。第一个通过常量引用获取,第二个则不。因此,第二个对象与 13.3.3.2/3b1sb4的非常数 *this对象(隐含的对象参数)更匹配,用于转换为 B*以执行第二个 check函数。

如果你将 拿开的常数,我们将有以下的候选人

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

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

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

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

Notice that since we never needed to 事实上 convert to a base class, we can thereby recognize 私人遗产 because whether we can convert from a D* to a B* isn't dependent on the form of inheritance according to 4.10/3

如果他们没有血缘关系

现在让我们假设它们不是通过继承关联的

D* (Host<B, D>&)

现在我们有了另一组

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

因为如果没有继承关系,就无法将 D*转换为 B*,所以现在在两个用户定义的转换序列中没有通用的转换函数!因此,如果不是因为第一个函数是一个模板,我们将是 模棱两可。模板是第二选择时,有一个非模板函数,是同样好的根据 13.3.3/1。因此,我们选择了非模板函数(第二个) ,并且我们认识到在 BD之间没有继承!

在第二个问题之后,请注意,如果没有 const,那么如果用 B = = D 实例化 Host,那么它将是格式不正确的。但是 is _ base _ of 的设计使得每个类都是它自己的基,因此其中一个转换运算符必须是 const。