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.