IT박스

`is_base_of`는 어떻게 작동합니까?

itboxs 2020. 7. 20. 07:50
반응형

`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];
  1. 그것은 B개인 기지입니다. 어떻게 작동합니까?

  2. 참고 operator B*()CONST입니다. 왜 중요 함?

  3. 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.2Host<B, D>D*B*check13.3.1.5/1

D* (Host<B, D>&)

로 변환 B*할 수 없으므로 첫 번째 변환 함수는 후보가 아닙니다 D*.

두 번째 기능의 경우 다음 후보가 존재합니다.

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

이들은 호스트 객체를 취하는 두 가지 변환 함수 후보입니다. 첫 번째는 const 참조로 사용하고 두 번째는 그렇지 않습니다. 따라서 두 번째는 비 const *this객체 ( 암시 적 객체 인수 ) 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*. 따라서 첫 번째 기능이 선택되고 상속을 인식합니다!

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 a B* isn't dependent on the form of inheritance according to 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 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 to 13.3.3/1. Thus, we select the non-template function (second one) and we recognize that there is no inheritance between B and D!


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)

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.

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*.

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


The private bit is completely ignored by is_base_of because overload resolution occurs before accessibility checks.

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


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.


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.

참고URL : https://stackoverflow.com/questions/2910979/how-does-is-base-of-work

반응형