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: 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.
If I'm not mistaken, the use 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.

+3


source to share


1 answer


  • 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

  1. template <typename Base> std::false_type is_base_of_test_func( void* );

this overload will match any type with the lowest priority

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

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

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

  1. 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)

  1. 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;
}

      

+4


source







All Articles