Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Explanation of possible implementation of std::is_base_of

Tags:

c++

c++11

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 pretty much straight forward. But, line 3: the using there is extremely vague, because I cannot simply replace every occurrence of pre_is_base_of with its definition. Ergo, the using is not really what the documentation says. It involves some religion as well. If I'm not wrong, the usage of pre_is_base_of should return std::true_type or std::false_type.
I'm equally lost when it comes to void_t. What kind of magic will that line do?
Shouldn't both implementations of pre_is_base_of2 take 3 types? What's the point of inheritance in line 6? There is probably more, but lets stop now.

I would need some detailed explanation on magic involved here. Basically I'm trying to understand how that code works.

Edit: When default asked me what's the error, I replaced every occurrence of pre_is_base_of and now there is no error.

like image 447
Malik Urac Avatar asked Dec 23 '22 18:12

Malik Urac


1 Answers

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

When the argument is a 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 calling is_base_of_test_func with a pointer to Derived. If Derived is derived from Base, it will return std::true_type, otherwise the void* overload will be selected and it will return a std::false_type. Now we have converted a function call result into a type.

  1. template <typename Base, typename Derived, typename = void> struct pre_is_base_of2 : public std::true_type {};

General case, this will be a true_type. Since the 3rd template argument is defaulted, this will be the version of the class defined when no other specialisation is created.

  1. template<typename ...> using void_t = void;

This is an easier way of doing 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 void_t is a legal type (i.e. pre_is_base_of<Base>(Derived*) is a valid expression, this will be the specialisation of pre_is_base_of2, which will evaluate to the decltype of calling the test function, above. It will only be selected if pre_is_base_of<Base,Derived> is a valid type (i.e. there exists a call to the test function)

  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> { };

essentially this is saying:

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 program 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;
}
like image 90
Richard Hodges Avatar answered Jan 28 '23 20:01

Richard Hodges