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


118

다음 코드는 어떻게 작동합니까?

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. 그리고 이것은 광범위한 컴파일러에서 작동합니다.


4
템플릿 매개 변수와 실제 클래스 이름에 동일한 식별자를 사용하는 것은 매우 혼란 스럽습니다.
Matthieu M.

1
@Matthieu M., 내가 올바른 : 나 자신에 그것을했다 한
키릴 V. Lyadvinsky에게

2
얼마 전에 ideone.com/T0C1V 의 대체 구현을 작성했습니다 is_base_of. 그래도 이전 GCC 버전에서는 작동하지 않습니다 (GCC4.3은 잘 작동합니다).
Johannes Schaub-litb

3
알겠습니다. 산책하겠습니다.
jokoon

2
이 구현은 올바르지 않습니다. is_base_of<Base,Base>::value이어야합니다 true; 이것은를 반환합니다 false.
chengiz

답변:


109

관련이있는 경우

잠시 동안 그것이 B실제로 D. 그런 다음에 대한 호출의 경우 로 변환 할 수 check있기 때문에 두 버전이 모두 실행 가능 합니다. from to 및 각각 에서 설명하는 사용자 정의 변환 시퀀스 입니다. 클래스를 변환 할 수있는 변환 함수를 찾기 위해 다음과 같은 후보 함수를 첫 번째 함수 로 합성합니다.HostD* 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 참조로 가져오고 두 번째는 그렇지 않습니다. 따라서 두 번째는에 의해 상수가 아닌 *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*. 따라서 첫 번째 기능이 선택되고 상속을 인식합니다!

우리가 필요하지도 것을 알 수 실제로 기본 클래스로 변환, 우리하여 인식 할 수있는 개인 상속을 우리가에서 변환 할 수 있는지 여부 있기 때문에 D*A와가 B*에 따라 상속의 형태에 의존하지 않는다4.10/3

관련이없는 경우

이제 상속과 관련이 없다고 가정 해 봅시다. 따라서 첫 번째 기능의 경우 다음 후보가 있습니다.

D* (Host<B, D>&) 

그리고 두 번째로 우리는 이제 다른 세트가 있습니다

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

우리는 변환 할 수 없습니다 이후 D*B*우리가 상속 관계를 가지고하지 않은 경우, 우리는 지금 두 개의 사용자 정의 변환 시퀀스 사이에 공통 변환 기능이 없다! 따라서 첫 번째 함수가 템플릿이라는 사실이 아니라면 모호 합니다. 에 따라 똑같이 좋은 템플릿이 아닌 기능이있을 때 템플릿이 두 번째 선택 13.3.3/1입니다. 따라서, 우리는 비 템플릿 함수 (두 번째)을 선택하고 우리 간의 상속 없다는 것을 인식 B하고 D!


2
아! Andreas는 단락이 맞았고 너무 나쁘게 대답하지 않았습니다. :) 시간을 내 주셔서 감사합니다.
Matthieu M.

2
이것이 제가 가장 좋아하는 대답이 될 것입니다. 질문 : 전체 C ++ 표준을 읽었습니까 아니면 C ++위원회에서 일하고 있습니까 ?? 축하합니다!
Marco A.

4
@DavidKernin이 C ++ 커밋에서 작업한다고해서 C ++의 작동 방식을 자동으로 알 수는 없습니다. :) 따라서 내가 한 세부 사항을 알기 위해 필요한 표준 부분을 반드시 읽어야합니다. 모든 것을 읽지 않았기 때문에 대부분의 표준 라이브러리 또는 스레딩 관련 질문에 대해서는 확실히 도움을 드릴 수 없습니다. :)
Johannes Schaub-litb

1
@underscore_d 공정하게 말하자면, 사양은 표준 라이브러리 임 플레 머가 .NET 과 같은 것을 사용할 수 있도록 일부 컴파일러 마법을 사용하는 std :: 특성을 금지하지 않습니다 . 컴파일 시간과 메모리 사용 속도를 높이는 데 도움이되는 템플릿 곡예를 피할 수 있습니다. 인터페이스가 다음과 같은 경우에도 마찬가지 std::is_base_of<...>입니다. 모든 것이 내부에 있습니다.
Johannes Schaub-litb

2
물론 일반적인 라이브러리는 boost::사용하기 전에 이러한 내장 함수를 사용할 수 있는지 확인해야합니다. 그리고 나는 컴파일러의 도움없이 무언가를 구현하기 위해 그들 사이에 일종의 "도전이 받아 들여진"사고 방식이 있다고 느낍니다. :)
Johannes Schaub-litb

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* constconst 여야 하는지 알 수 있습니다. 그렇지 않으면 Host<B,D> -> Host<B,D> const단계 가 필요 없고 모호하지 않으며 no check(B*, int)항상 선택됩니다.


귀하의 설명은의 존재를 설명하지 않습니다 const. 귀하의 답변이 사실이면 아니오 const가 필요합니다. 그러나 그것은 사실이 아닙니다. 제거 const하고 트릭이 작동하지 않습니다.
Alexey Malistov

const가 없으면에 대한 두 개의 변환 시퀀스 no check(B*, int)가 더 이상 모호하지 않습니다.
MSalters

을 남겨두면 no check(B*, int)관련 BD에 대해 모호하지 않습니다. 컴파일러는 operator D*()const가 없기 때문에 변환을 수행 하도록 선택 합니다. 그것은 오히려 반대 방향으로 조금 : 당신이 경우 제거 를 const, 당신은 모호함의 어떤 의미를 소개하지만, 이는 사실에 의해 해결 operator B*()에 대한 포인터 변환이 필요하지 않습니다 우수한 반환 형식 제공 B*과 같이 D*수행합니다.
Johannes Schaub-litb

그것이 실제로 요점입니다. 모호성은 두 개의 서로 다른 변환 시퀀스 사이 B*<Host<B,D>()임시 에서 가져옵니다 .
MSalters

이것이 더 나은 대답입니다. 감사! 그래서 내가 이해했듯이 한 기능이 더 좋지만 모호하다면 다른 기능이 선택됩니까?
user1289

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. 사적 기반 이라는 사실 은 수표가 발생하는 것을 막지 않고 전환을 막을 뿐이며 실제 전환을 요구하지 않습니다.)


일종의. 기본 변환은 전혀 수행되지 않습니다. 평가되지 않은 표현식 host으로 D*또는 임의로 변환됩니다 B*. 어떤 이유로 특정 조건에서 D*보다 선호 B*됩니다.
Potatoswatter

대답은 13.3.1.1.2에 있다고 생각하지만 아직 세부 사항을 정리하지 않았습니다. :)
Andreas Brinck

내 대답은 "왜 사적인 작업을 하는가"부분만을 설명하고 있으며, 사례에 따라 전체 해결 과정에 대한 명확한 설명을 간절히 기다리고 있지만 sellibitze의 대답은 확실히 더 완벽합니다.
Matthieu M.

2

부분 순서 wrt 과부하 해결과 관련이있을 수 있습니다. D가 B에서 파생되는 경우 D *는 B *보다 더 전문화됩니다.

정확한 세부 사항은 다소 복잡합니다. 다양한 과부하 해결 규칙의 우선 순위를 파악해야합니다. 부분 주문이 하나입니다. 길이 / 종류의 변환 시퀀스는 또 다른 것입니다. 마지막으로, 두 개의 실행 가능한 기능이 똑같이 좋은 것으로 간주되면 기능 템플릿보다 비 템플릿이 선택됩니다.

이 규칙이 어떻게 상호 작용하는지 찾아 볼 필요가 없었습니다. 그러나 부분 순서가 다른 과부하 해결 규칙을 지배하는 것 같습니다. D가 B에서 파생되지 않으면 부분 정렬 규칙이 적용되지 않으며 비 템플릿이 더 매력적입니다. D가 B에서 파생되면 부분 순서가 시작되고 기능 템플릿이 더 매력적으로 보입니다.

상속이 privete 인 경우 코드는 공용 상속이 필요한 D *에서 B * 로의 변환을 요청하지 않습니다.


나는 그것이 그런 것이라고 생각한다. 나는 그것을 is_base_of보장하기 위해 기여자들이 겪은 루프와 구현에 대한 부스트 아카이브에 대한 광범위한 토론을 본 기억 이있다.
Matthieu M.

The exact details are rather complicated-그게 요점입니다. 설명 해주십시오. 알고 싶습니다.
Alexey Malistov

@Alexey : 글쎄, 나는 당신을 올바른 방향으로 가르쳤다 고 생각했습니다. 이 경우 다양한 과부하 해결 규칙이 어떻게 상호 작용하는지 확인하십시오. 이 과부하 사례의 해결과 관련하여 B에서 파생 된 D와 B에서 파생되지 않은 D의 유일한 차이점은 부분 정렬 규칙입니다. 오버로드 해결은 C ++ 표준의 §13에 설명되어 있습니다. 무료 초안을받을 수 있습니다 : open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1804.pdf
sellibitze

오버로드 해결은 해당 초안에서 16 페이지에 걸쳐 있습니다. 이 경우 규칙과 규칙 간의 상호 작용을 정말로 이해해야한다면 전체 섹션 §13.3을 읽어야합니다. 나는 여기서 100 % 정확하고 당신의 기준에 맞는 답을 얻는 것을 믿지 않을 것입니다.
sellibitze

관심이 있으시면 설명에 대한 제 답변을 참조하십시오.
Johannes Schaub-litb

0

두 번째 질문에 따라 const가 아니라면 Host가 B == D로 인스턴스화되면 형식이 잘못되었습니다. 그러나 is_base_of는 각 클래스가 자체의 기반이되도록 설계되었으므로 변환 연산자 중 하나는 const가 되십시오.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.