7

I have written a small utility for testing whether or not a type has inherited some template instantiation of a specific template class, either directly or trough inheriting a class that inherits the template. This is accomplished with a SFINAE check using a template function accepting any template instantiation of the provided template and a fallback overload for the default case.

#include <iostream>
#include <type_traits>

template<template<class> class T, class U>
struct isDerivedFrom
{
    static constexpr bool value = decltype(isDerivedFrom::test(U()))::value;
private:
    template<class V>
    static std::true_type test(T<V>);
    static std::false_type test(...);
};

template<class T>
struct Base {};
struct Base_D1 : Base<int> {};
struct Base_D2 : Base<Base_D2> {};
struct Base_D1_D1 : Base_D1 {};
struct NotDerived {};

int main()
{
    std::cout << std::boolalpha
        << "is Base_D1 derived from or a template instantiation of Base: "
        << isDerivedFrom<Base, Base_D1>::value << "\n"
        << "is Base_D2 derived from or a template instantiation of Base: "
        << isDerivedFrom<Base, Base_D2>::value << "\n"
        << "is Base_D1_D1 derived from or a template instantiation of Base: "
        << isDerivedFrom<Base, Base_D1_D1>::value << "\n"
        << "is Base<double> derived from or a template instantiation of Base: "
        << isDerivedFrom<Base, Base<double>>::value << "\n"
        << "is NotDerived derived from or a template instantiation of Base: "
        << isDerivedFrom<Base, NotDerived>::value << "\n";
    return 0;
}

Output:

is Base_D1 derived from or a template instantiation of Base: true
is Base_D2 derived from or a template instantiation of Base: true
is Base_D1_D1 derived from or a template instantiation of Base: true
is Base<double> derived from or a template instantiation of Base: true
is NotDerived derived from or a template instantiation of Base: false

My problem is that if the type to be tested (template argument T of isDerivedFrom) has or inherits a non-public constructor or inherits the template trough non-public inheritance, it causes a compile error because decltype(T()) fails if T::T() is not public:

struct Base {protected: Base(){}};
struct Derived : private Base<int> {};

Is there any way to make this work for all cases? Are there any unmentioned issues with the code?

jms
  • 719
  • 2
  • 8
  • 18
  • There used to be a trait `std::bases` and `std::direct_bases` part of the abandoned TR2, which is still shipped with GCC. You could use that to check if any of the bases are template instantiations. – Kerrek SB Mar 23 '14 at 15:14
  • 1
    This code is not correct because if `U` inherits from `T` and `T` where `X` != `Y`, it does not detect the inheritance. – Johannes Schaub - litb Mar 23 '14 at 15:34
  • BTW: You may have compile time check with `static_assert(isDerivedFrom::value, "unexpected value")` instead of manual visual check with stream. – Jarod42 Mar 23 '14 at 15:36
  • @Jarod42 Sure, thats why I wrote the code in the first place, printing to console just felt more natural for testing. – jms Mar 23 '14 at 15:41
  • @JohannesSchaub The case of multiple inheritance did not occur to me, good catch. What solution do you suggest? – jms Mar 23 '14 at 15:41
  • @user1062874 i don't know. – Johannes Schaub - litb Mar 23 '14 at 15:46
  • What about `(*(T*)0)` instead of `T()`? Since the code in `decltype` is not executed, I think this should not be undefined behaviour – celtschk Jan 15 '17 at 19:01

1 Answers1

15

You may use: https://ideone.com/wR2dLX

template<template<class> class T, class U>
struct isDerivedFrom
{
private:
    template<class V>
    static decltype(static_cast<const T<V>&>(std::declval<U>()), std::true_type{})
    test(const T<V>&);

    static std::false_type test(...);
public:
    static constexpr bool value = decltype(isDerivedFrom::test(std::declval<U>()))::value;
};

As private inheritance is not visible, the trait returns false in the last case (for struct Derived : private Base<int> {};).

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Could you please explain what `decltype(static_cast>(std::declval()), std::true_type{})` does? Why is the static_cast required? – jms Mar 23 '14 at 15:30
  • The `static_cast` is to try to (publicly) converts `U` to `T` and use SFINAE to remove invalid case. (your code doesn't use SFINAE, just use the better match). – Jarod42 Mar 23 '14 at 15:43
  • `operator T` can fool this? – Yakk - Adam Nevraumont Mar 23 '14 at 16:04
  • @Yakk no, because a conversion function is never invoked to convert to a base class type. And if `T` is not a base-class type, template argument deduction would have failed before trying to do the conversion. – Johannes Schaub - litb Mar 23 '14 at 16:25
  • 1
    However, I belive that the `static_cast` can be fooled, if `T` has a public constructor `T(const U&)`. You may want to change the static_cast to `static_cast&>`, which should eliminate this loophole. – Johannes Schaub - litb Mar 23 '14 at 16:49