C++ `is_base_of` 是如何工作的?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2910979/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 11:28:24  来源:igfitidea点击:

How does `is_base_of` work?

c++templatesoverloadingimplicit-conversiontypetraits

提问by Alexey Malistov

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 Bis 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);?

  1. 请注意,这B是私人基地。这是如何运作的?

  2. 请注意,这operator B*()是常量。它为什么如此重要?

  3. 为什么template<typename T> static yes check(D*, T);比 好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.

注意:它是boost::is_base_of. 这适用于广泛的编译器。

采纳答案by Johannes Schaub - litb

If they are related

如果他们有关系

Let's for a moment assume that Bis actually a base of D. Then for the call to check, both versions are viable because Hostcan be converted to D*andB*. It's a user defined conversion sequence as described by 13.3.3.1.2from Host<B, D>to D*and B*respectively. For finding conversion functions that can convert the class, the following candidate functions are synthesized for the first checkfunction according to 13.3.1.5/1

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

D* (Host<B, D>&)

The first conversion function isn't a candidate, because B*can't be converted to D*.

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

For the second function, the following candidates exist:

对于第二个函数,存在以下候选:

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

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 *thisobject (the implied object argument) by 13.3.3.2/3b1sb4and is used to convert to B*for the second checkfunction.

这些是采用宿主对象的两个转换函数候选者。第一个通过 const 引用获取它,第二个没有。因此,第二个是非常量*this对象(隐含的对象参数)的更好匹配by13.3.3.2/3b1sb4并且用于转换B*为第二个check函数。

If you would removethe const, we would have the following candidates

如果您要删除常量,我们将有以下候选者

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

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, because B*converts better to B*than D*to B*.

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

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 constis important because it forces the compiler to take the second conversion function.

现在什么用户定义的转换顺序更好?用于第二个或第一个检查功能的那个?规则是用户定义的转换序列只能在它们根据 使用相同的转换函数或构造函数时进行比较13.3.3.2/3b2。这正是这里的情况:两者都使用第二个转换函数。请注意,因此const很重要,因为它强制编译器采用第二个转换函数。

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 to D*than to B*. Thus the first function is selected and we recognize the inheritance!

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

Notice that since we never needed to actuallyconvert to a base class, we can thereby recognize private inheritancebecause whether we can convert from a D*to a B*isn't dependent on the form of inheritance according to 4.10/3

请注意,由于我们从来不需要实际转换为基类,因此我们可以识别私有继承,因为我们是否可以从 a 转换D*为 aB*不依赖于根据继承的形式4.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

现在让我们假设它们没有继承关系。因此对于第一个函数,我们有以下候选

D* (Host<B, D>&) 

And for the second we now have another set

对于第二个我们现在有另一组

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

Since we cannot convert D*to B*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 ambiguousif 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 to 13.3.3/1. Thus, we select the non-template function (second one) and we recognize that there is no inheritance between Band D!

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

回答by MSalters

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 this check(...)is a function call expression, so it needs to do overload resolution on check. There are two candidate overloads available, template <typename T> yes check(D*, T);and no check(B*, int);. If the first is chosen, you get sizeof(yes), else sizeof(no)

sizeof(check(Host<B,D>(), int()))零件开始。编译器可以很快看出这check(...)是一个函数调用表达式,因此需要对 进行重载解析check。有两个候选重载可用,template <typename T> yes check(D*, T);no check(B*, int);. 如果选择第一个,你会得到sizeof(yes),否则sizeof(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 is check(B*, int). The actual arguments provided are Host<B,D>and int(). 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.

接下来,让我们看看重载决议。第一个重载是模板实例化check<int> (D*, T=int),第二个候选是check(B*, int). 提供的实际参数是Host<B,D>int()。第二个参数显然没有区分它们;它只是使第一个重载成为模板。我们稍后会看到为什么模板部分是相关的。

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 via Host<B,D>::operator B*() const. If (and only if) B and D are related by inheritance will the conversion sequence Host<B,D>::operator D*()+ D*->B*exist. Now assume D indeed inherits from B. The two conversion sequences are Host<B,D> -> Host<B,D> const -> operator B* const -> B*and Host<B,D> -> operator D* -> D* -> B*.

现在查看所需的转换序列。对于第一个重载,我们有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*

So, for related B and D, no check(<Host<B,D>(), int())would ambiguous. As a result, the templated yes check<int>(D*, int)is chosen. However, if D does not inherit from B, then no 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).

所以,对于相关的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).

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 the operator B* constmust be const: else there's no need for the Host<B,D> -> Host<B,D> conststep, no ambiguity, and no check(B*, int)would always be chosen.

您现在明白为什么继承是私有的并不重要:该关系仅用于no check(Host<B,D>(), int())在访问检查发生之前从重载解析中消除。你也明白为什么operator B* const必须是常量:否则就不需要Host<B,D> -> Host<B,D> const步骤,没有歧义,并且no check(B*, int)总是会被选中。

回答by Matthieu M.

The privatebit is completely ignored by is_base_ofbecause overload resolution occurs before accessibility checks.

由于重载解析发生在可访问性检查之前privateis_base_of因此该位被完全忽略。

You can verify this simply:

您可以简单地验证这一点:

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 Bis 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 ;)

这同样适用于这里,B作为私人基地的事实不会阻止检查的发生,它只会阻止转换,但我们从不要求实际转换;)

回答by sellibitze

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.

它可能与偏序重载解析有关。在 D 派生自 B 的情况下,D* 比 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.

我从来不需要查看这些规则是如何相互作用的。但似乎偏序支配了其他重载解析规则。当 D 不是从 B 派生出来时,偏序规则不适用,非模板更有吸引力。当 D 派生自 B 时,偏序开始起作用并使函数模板更具吸引力——正如它看起来的那样。

As for inheritance being privete: the code never asks for a conversion from D* to B* which would require public inheritence.

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

回答by Hertz

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.

在第二个问题之后,请注意,如果不是 const,如果使用 B == D 实例化 Host 将是格式错误的。但是 is_base_of 的设计使得每个类都是其自身的基础,因此转换运算符之一必须是常量。