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 If I'm not wrong, the usage of 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.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.
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
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 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.
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.
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.
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)
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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With