is_base_of 特征的实现是如何工作的?
下面的代码是如何工作的?
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
是私有基。这是如何工作的? -
请注意,
运算符 B*()
是 const。为什么它很重要? -
为什么
template
比是? static yes check(D*, T); static yes check(B*, int);
更好?
注意:它是boost::is_base_of
的简化版本(宏被删除)。这适用于多种编译器。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
如果它们相关,
我们暂时假设
B
实际上是D
的基础。然后对于check
的调用,两个版本都是可行的,因为Host
可以转换为D*
和B*
。它是用户定义的转换序列,如13.3.3.1.2
中所述,从Host
到D*
和B * 分别。为了找到可以转换类的转换函数,根据
13.3.1.5/1
为第一个check
函数合成了以下候选函数。第一个转换函数不是一个候选,因为
B*
无法转换为D*
。对于第二个函数,存在以下候选函数:
这些是采用宿主对象的两个转换函数候选函数。第一个通过 const 引用获取它,第二个则不这样做。因此,第二个是
13.3.3.2/3b1sb4
的非常量*this
对象(隐含对象参数)更好的匹配,并且是用于转换为第二个check
函数的B*
。如果您删除常量,我们将有以下候选者。
这意味着我们不能再通过常量进行选择。在普通的重载决策场景中,调用现在将不明确,因为通常返回类型不会参与重载决策。然而,对于转换函数来说,存在一个后门。如果两个转换函数同样好,那么它们的返回类型根据
13.3.3/1
决定谁最好。因此,如果您要删除 const,则将采用第一个,因为B*
转换为B*
比D*
转换为 <代码>B*。现在什么用户定义的转换顺序更好?用于第二个或第一个检查功能的那个?规则是,只有根据
13.3.3.2/3b2
使用相同的转换函数或构造函数,用户定义的转换序列才能进行比较。这里的情况正是如此:两者都使用第二个转换函数。请注意,const 很重要,因为它强制编译器采用第二个转换函数。既然我们可以比较它们 - 哪一个更好?规则是从转换函数的返回类型到目标类型的更好的转换获胜(同样是
13.3.3.2/3b2
)。在这种情况下,D*
转换为D*
比转换为B*
更好。因此,第一个函数被选择,我们认识到继承!请注意,由于我们实际上从未需要转换为基类,因此我们可以识别私有继承,因为我们是否可以从
D*
转换根据4.10/3
,B*
不依赖于继承形式如果它们不相关
现在让我们假设它们不通过继承相关。因此,对于第一个函数,我们有以下候选函数
对于第二个函数,我们现在有另一个集合
因为如果我们没有继承,我们就无法将
D*
转换为B*
关系,我们现在在两个用户定义的转换序列之间没有通用的转换函数!因此,如果不是因为第一个函数是模板这一事实,我们就会模棱两可。根据13.3.3/1
,当存在同样好的非模板函数时,模板是第二选择。因此,我们选择非模板函数(第二个),并且我们认识到B
和D
之间没有继承!If they are related
Let's for a moment assume that
B
is actually a base ofD
. Then for the call tocheck
, both versions are viable becauseHost
can be converted toD*
andB*
. It's a user defined conversion sequence as described by13.3.3.1.2
fromHost<B, D>
toD*
andB*
respectively. For finding conversion functions that can convert the class, the following candidate functions are synthesized for the firstcheck
function according to13.3.1.5/1
The first conversion function isn't a candidate, because
B*
can't be converted toD*
.For the second function, the following candidates exist:
Those are the two conversion function candidates that take the host object. The first takes it by const reference, and the second doesn't. Thus the second is a better match for the non-const
*this
object (the implied object argument) by13.3.3.2/3b1sb4
and is used to convert toB*
for the secondcheck
function.If you would remove the const, we would have the following candidates
This would mean that we can't select by constness anymore. In an ordinary overload resolution scenario, the call would now be ambiguous because normally the return type won't participate in overload resolution. For conversion functions, however, there is a backdoor. If two conversion functions are equally good, then the return type of them decides who is best according to
13.3.3/1
. Thus, if you would remove the const, then the first would be taken, becauseB*
converts better toB*
thanD*
toB*
.Now what user defined conversion sequence is better? The one for the second or the first check function? The rule is that user defined conversion sequences can only be compared if they use the same conversion function or constructor according to
13.3.3.2/3b2
. This is exactly the case here: Both use the second conversion function. Notice that thus the const is important because it forces the compiler to take the second conversion function.Since we can compare them - which one is better? The rule is that the better conversion from the return type of the conversion function to the destination type wins (again by
13.3.3.2/3b2
). In this case,D*
converts better toD*
than toB*
. Thus the first function is selected and we recognize the inheritance!Notice that since we never needed to actually convert to a base class, we can thereby recognize private inheritance because whether we can convert from a
D*
to aB*
isn't dependent on the form of inheritance according to4.10/3
If they are not related
Now let's assume they are not related by inheritance. Thus for the first function we have the following candidates
And for the second we now have another set
Since we cannot convert
D*
toB*
if we haven't got a inheritance relationship, we now have no common conversion function among the two user defined conversion sequences! Thus, we would be ambiguous if not for the fact that the first function is a template. Templates are second choice when there is a non-template function that is equally good according to13.3.3/1
. Thus, we select the non-template function (second one) and we recognize that there is no inheritance betweenB
andD
!让我们通过查看步骤来了解它是如何工作的。
从
sizeof(check(Host(), int()))
部分开始。编译器很快就能看出这个check(...)
是一个函数调用表达式,因此需要对check
做重载解析。有两个候选重载可用,template 和
template 。是检查(D*, T);
和不检查(B*, int);
。如果选择第一个,您将得到sizeof(yes)
,否则sizeof(no)
接下来,让我们看看重载决策。第一个重载是模板实例化
check; (D*, T=int)
第二个候选是check(B*, int)
。提供的实际参数是Host
和int()
。第二个参数显然没有区分它们;它只是使第一个重载成为模板重载。稍后我们将看到为什么模板部分是相关的。现在看看所需的转换序列。对于第一个重载,我们有
Host::operator D*
- 一个用户定义的转换。对于第二个,过载更加棘手。我们需要一个 B*,但可能有两个转换序列。一种是通过 Host::operator B*() const。如果(且仅当)B 和 D 通过继承相关时,转换序列Host::operator D*()
+D*->B*存在。现在假设D确实继承自B。这两个转换序列是Host和Host。 ->主机常量 ->运算符 B* const -> B*
和Host ->运算符 D* -> D*-> B*
。因此,对于相关的 B 和 D,(D*, int)。但是,如果 D 不是从 B 继承,则 no check((), int()) 是明确的。此时,无法基于最短转换序列进行重载决策。然而,在给定相同的转换序列的情况下,重载解析更喜欢非模板函数,即
no check((), int())
会产生歧义。结果,选择了模板化的 yes checkno check(B*, int)
。您现在明白为什么继承是私有的并不重要:该关系仅用于消除访问之前重载解析中的 no check(Host(), int())检查发生。您还会明白为什么运算符 B* const 必须是 const:否则就不需要 Host 了。 ->主机 const 步骤,没有歧义,并且始终选择
no check(B*, int)
。Let's work out how it works by looking at the steps.
Start with the
sizeof(check(Host<B,D>(), int()))
part. The compiler can quickly see that thischeck(...)
is a function call expression, so it needs to do overload resolution oncheck
. There are two candidate overloads available,template <typename T> yes check(D*, T);
andno check(B*, int);
. If the first is chosen, you getsizeof(yes)
, elsesizeof(no)
Next, let's look at the overload resolution. The first overload is a template instantiation
check<int> (D*, T=int)
and the second candidate ischeck(B*, int)
. The actual arguments provided areHost<B,D>
andint()
. The second parameter clearly doesn't distinguish them; it merely served to make the first overload a template one. We'll see later why the template part is relevant.Now look at the conversion sequences that are needed. For the first overload, we have
Host<B,D>::operator D*
- one user-defined conversion. For the second, the overload is trickier. We need a B*, but there are possibly two conversion sequences. One is viaHost<B,D>::operator B*() const
. If (and only if) B and D are related by inheritance will the conversion sequenceHost<B,D>::operator D*()
+D*->B*
exist. Now assume D indeed inherits from B. The two conversion sequences areHost<B,D> -> Host<B,D> const -> operator B* const -> B*
andHost<B,D> -> operator D* -> D* -> B*
.So, for related B and D,
no check(<Host<B,D>(), int())
would ambiguous. As a result, the templatedyes check<int>(D*, int)
is chosen. However, if D does not inherit from B, thenno check(<Host<B,D>(), int())
is not ambiguous. At this point, overload resolution cannot happen based on shortest conversion sequence. However, given equal conversion sequences, overload resolution prefers non-template functions, i.e.no check(B*, int)
.You now see why it doesn't matter that the inheritance is private: that relation only serves to eliminate
no check(Host<B,D>(), int())
from overload resolution before the access check happens. And you also see why theoperator B* const
must be const: else there's no need for theHost<B,D> -> Host<B,D> const
step, no ambiguity, andno check(B*, int)
would always be chosen.private
位被is_base_of
完全忽略,因为重载解析发生在可访问性检查之前。您可以简单地验证这一点:
同样适用于此,事实上
B
是私有基数并不会阻止检查的发生,它只会阻止转换,但我们从不要求实际转换;)The
private
bit is completely ignored byis_base_of
because overload resolution occurs before accessibility checks.You can verify this simply:
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 ;)它可能与重载解析的部分排序有关。如果 D 派生自 B,则 D* 比 B* 更专业。
具体细节相当复杂。您必须弄清楚各种重载解析规则的优先级。部分排序就是其中之一。转换序列的长度/种类是另一回事。最后,如果两个可行的函数被认为同样好,则选择非模板而不是函数模板。
我从来不需要查找这些规则如何相互作用。但似乎部分排序主导了其他重载解析规则。当 D 不是从 B 派生时,部分排序规则不适用,并且非模板更具吸引力。当 D 从 B 派生时,偏序就开始发挥作用,使函数模板变得更有吸引力——正如看起来的那样。
至于继承是私有的:代码从不要求从 D* 到 B* 的转换,而这需要公共继承。
It possibly has something to do with partial ordering w.r.t. overload resolution. D* is more specialized than B* in case D derives from B.
The exact details are rather complicated. You have to figure out the precedences of various overload resolution rules. Partial ordering is one. Lengths/kinds of conversion sequences is another one. Finally, if two viable functions are deemed equally good, non-templates are chosen over function templates.
I've never needed to look up how these rules interact. But it seems partial ordering is dominating the other overload resolution rules. When D doesn't derive from B the partial ordering rules don't apply and the non-template is more attractive. When D derives from B, partial ordering kicks in and makes the function template more attractive -- as it seems.
As for inheritance being privete: the code never asks for a conversion from D* to B* which would require public inheritence.
接下来你的第二个问题,请注意,如果不是 const,如果用 B == D 实例化,Host 将是格式错误的。但是 is_base_of 的设计使得每个类都是其自身的基数,因此转换运算符之一必须是常数。
Following on your second question, note that if it weren't for const, Host would be ill-formed if instantiated with B == D. But is_base_of is designed such that each class is a base of itself, hence one of conversion operators must be const.