Home > Software design >  understanding std::is_base_of possible implementation from cppreference
understanding std::is_base_of possible implementation from cppreference

Time:10-24

Here we have is_base_of template implementation taken from cppreference.com:

namespace details {
     template <typename B>
     std::true_type test_pre_ptr_convertible(const B*);      //1
     template <typename>
     std::false_type test_pre_ptr_convertible(const void*);  //2

     template <typename, typename>
     auto test_pre_is_base_of(...)->std::true_type;          //3
     template <typename B, typename D>
     auto test_pre_is_base_of(int) ->
         decltype(test_pre_ptr_convertible<B>(static_cast<D*>(nullptr))); 
 }

template <typename Base, typename Derived>
 struct is_base_of :
     std::integral_constant<
     bool,
     std::is_class<Base>::value&& std::is_class<Derived>::value&&
     decltype(details::test_pre_is_base_of<Base, Derived>(0))::value
     > { };

And some private inheritance:

class A {};
class B : A {};

is_base_of<A,B>::value gives true and declaration no. 3 is the best match. Declaration no. 1 fails as a candidate(was passed a pointer to object of private subclass) and declaration no. 2 is ignored. But Why? Isn't void* a good match for every pointer type? If declaration no. 3 was not provided the code wouldn't compile. My question is why declaration no. 3 needs to be provided for this code to compile successfully? Why declarations no. 1 and no. 2 aren't sufficient?

CodePudding user response:

Overload resolution ignores accessibility for the purpose of deciding which candidates are viable and for choosing the best candidate.

If D is a derived class of B (irregardless of accessibility), then //1 is always viable and a better candidate for overload resolution than //2 (which is also viable) and overload resolution will choose the former.

If //1 is chosen as best candidate and B is not a public base of D, then the required implicit conversion on the argument will however fail because B is not an accessible base. But that is already after overload resolution was done. There will be no attempt to then fall back to the "second best" overload //2.

Since the call to test_pre_ptr_convertible is therefore invalid, the whole program would normally be ill-formed. However, the call here is in the return type of a function template and so SFINAE applies, meaning that the overload

template <typename B, typename D>
auto test_pre_is_base_of(int) ->
    decltype(test_pre_ptr_convertible<B>(static_cast<D*>(nullptr)));

is simply not viable for the call from is_base_of and the only other remaining overload for test_pre_is_base_of is //3, which is then chosen.

  • Related