`is_base_of`如何工作?
-
04-10-2019 - |
题
以下代码如何工作?
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];
注意
B
是私人基地。这是如何运作的?注意
operator B*()
是const。它为什么如此重要?为什么是
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.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引用将其采用,第二个则没有。因此,第二个是对非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
. 。因此,我们选择非模板函数(第二个),我们认识到之间没有继承 B
和 D
!
其他提示
让我们通过查看步骤来弄清楚它的工作原理。
从 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。