以下代码如何运作?
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
的缩减版本(已删除宏)。这适用于各种编译器。
答案 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/1
和B
之间没有继承!
答案 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。