Explanation of possible implementation of std :: is_base_of
1. template <typename Base> std::true_type is_base_of_test_func( Base* );
2. template <typename Base> std::false_type is_base_of_test_func( void* );
3. template <typename Base, typename Derived>
using pre_is_base_of = decltype( is_base_of_test_func<Base>( std::declval<Derived*>() ) );
4. template <typename Base, typename Derived, typename = void>
struct pre_is_base_of2 : public std::true_type {};
5. template<typename ...> using void_t = void;
6. template <typename Base, typename Derived>
struct pre_is_base_of2<Base, Derived, void_t<pre_is_base_of<Base, Derived>>> : public pre_is_base_of<Base, Derived>{};
7. template <typename Base, typename Derived>
struct is_base_of : public std::conditional_t<std::is_class<Base>::value && std::is_class<Derived>::value,
pre_is_base_of2<Base, Derived>,
std::false_type>
{
};
Lines 1 and 2 are fairly straightforward. But line 3: If I'm not mistaken, the use using
there is extremely vague, because I can't just replace each occurrence pre_is_base_of
with my definition. Ergo, using
not really what the documentation says. This also applies to some kind of religion.pre_is_base_of
should return std::true_type
or std::false_type
.
I am lost when it comes to void_t
. What magic will this line do?
Shouldn't both implementations pre_is_base_of2
accept 3 types? What's the point of inheritance on line 6? There are probably more, but now let's stop.
I would need a detailed explanation about magic. I am mainly trying to understand how this code works.
Edit: When default asked me what the error was, I replaced each event pre_is_base_of
and now there is no error.
source to share
template <typename Base> std::true_type is_base_of_test_func( Base* );
When the argument is base or derived from base, this overload has the highest priority
template <typename Base> std::false_type is_base_of_test_func( void* );
this overload will match any type with the lowest priority
template <typename Base, typename Derived> using pre_is_base_of = decltype( is_base_of_test_func<Base>( std::declval<Derived*>() ) );
pre_is_base_of will become the type returned by the call is_base_of_test_func
with a pointer to Derived
. If Derived
inferred from Base it will return std :: true_type, otherwise the void * overload will be chosen and it will return std :: false_type. We have now converted the result of the function call to a type.
template <typename Base, typename Derived, typename = void> struct pre_is_base_of2 : public std::true_type {};
General case, this would be true_type. Since the 3rd template argument is the default, this will be the version of the class defined when no other specialization is created.
template<typename ...> using void_t = void;
This is an easier way to do enable_if. void_t<X>
will only be a type if X is a legal type.
template <typename Base, typename Derived> struct pre_is_base_of2<Base, Derived, void_t<pre_is_base_of<Base, Derived>>> : public pre_is_base_of<Base, Derived>{};
if it void_t
is a legal type (i.e. pre_is_base_of<Base>(Derived*)
is a valid expression, it will be a specialization pre_is_base_of2
that will be evaluated using the decltype method of the test function call above. It will only be selected if it pre_is_base_of<Base,Derived>
is a valid type (i.e. a test function call exists)
template <typename Base, typename Derived> struct is_base_of : public std::conditional_t<std::is_class<Base>::value && std::is_class<Derived>::value, pre_is_base_of2<Base, Derived>, std::false_type> { };
it basically says:
IF Base and Value are classes AND void_t<decltype(is_base_of_test_func<Base>(Derived*))> is a type
THEN
select the type of pre_is_base_of2<Base, Derived, void_t<...is the expression legal?...>>
ELSE
select false_type
Update:
Hopefully this little demo will provide some clarity:
#include <type_traits>
#include <iostream>
template<class...> using void_t = void;
// this expands in any case where no second type is provided
template<class T, typename = void> struct does_he_take_sugar : std::false_type {};
// the specialisation can only be valid when void_t<expr> evaluates to a type.
// i.e. when T has a member function called take_sugar
template<class T> struct does_he_take_sugar<T, void_t<decltype(std::declval<T>().take_sugar())>> : std::true_type {};
struct X {
int take_sugar();
};
struct Y {
int does_not();
};
int main()
{
// X::take_sugar is a function therefore void_t<decltype(...X)> will evaluate to void
std::cout << does_he_take_sugar<X>::value << std::endl;
// Y::take_sugar is not a function therefore void_t<decltype(...Y)> will not evaluate at all
std::cout << does_he_take_sugar<Y>::value << std::endl;
// int::take_sugar is not even valid c++ void_t<decltype(...int)> will not evaluate at all
std::cout << does_he_take_sugar<int>::value << std::endl;
}
source to share