15

Is is possible to get base class type in a class hierarchy?

For example:

struct A{};
struct B{} : public A;
struct C{} : public B;

I want some template that will have typedef Base<T>::Type inside like this:

Base<A>::Type == A
Base<B>::Type == A
Base<C>::Type == A

Is this possible? What about the case when I have multiple inheritance?

Mircea Ispas
  • 19,462
  • 29
  • 118
  • 205

4 Answers4

27

Classes in C++ can have more than one base class, so there's no sense in having a "get me the base" trait.

However, the TR2 additions include new compiler-supported traits std::tr2::bases and std::tr2::direct_bases, which returns an opaque type list of base classes.

I'm not sure whether this will make it into C++14, or whether it'll be released independently, but GCC already seems to support this.

Kerrek SB
  • 447,451
  • 88
  • 851
  • 1,056
  • That's a kind of a type trait that can't be implemented without compiler magic, is it? – jrok Apr 28 '13 at 12:13
  • 1
    This is more appropriate for my question, but I accepted the other answer because in combination with std::conditional it solves my problem. – Mircea Ispas Apr 28 '13 at 12:21
  • 1
    @jrok: well, obviously not, how would the trait learn about the types in the program ? On the other hand, this is the kind of little things that get us closer to a full compile-time reflection system. – Matthieu M. Apr 28 '13 at 12:40
  • 3
    @Felics how can this not be the accepted answer? you question was not whether `B` is indeed a base of `D` (mapping to bool), but which `B` is `D` derived from (one-to-many mapping). – TemplateRex Apr 28 '13 at 19:43
  • @rhalbersma I explained in a previous comment – Mircea Ispas Apr 28 '13 at 19:56
  • 6
    @Felics as an aside, in the future both describe your *real* problem and the problem you are having with your attempted solution. While stack overflow is decent at reading minds, it works even better if it doesn't have to! – Yakk - Adam Nevraumont Apr 28 '13 at 20:44
  • if compiler magic needed anyway why wrap it into STL? It should be direct keyword `base` accessible within classes. – Sergei Krivonos Oct 01 '17 at 05:52
  • @Sergei: Sorry, I don't follow -- how is a "direct keyword" anything other than compiler magic? – Kerrek SB Oct 01 '17 at 10:45
  • @KerrekSB, no-one said that it other, the question, why STL affected – Sergei Krivonos Oct 02 '17 at 14:30
8

I think std::is_base_of can help you

#include <type_traits>

std::is_base_of<B, D>()

If D is derived from B or if both are the same non-union class, provides the member constant value equal to true. Otherwise value is false.

You can use it to check if a class is base class of another or not :

std::is_base_of<A, A>()   // Base<A>::Type == A

std::is_base_of<A, B>()   // Base<B>::Type == A

std::is_base_of<A, C>()   // Base<C>::Type == A
masoud
  • 53,199
  • 16
  • 130
  • 198
8

This might be a nice way to do it, depending on your use case. Declare a typedef of the base class named base in the base class itself.

Then derived classes X will inherit it as the typename X::base.

So B::base is A, and C::base is A.

struct A
{
    typedef A base;
};

struct B : A {};
struct C : B {};

template<class X>
void f()
{
    typename X::base x;
}

int main()
{
    f<B>();
    f<C>();
}
Andrew Tomazos
  • 62,609
  • 36
  • 171
  • 294
1

With certain limitations, it's possible!

  • Each base that needs to be detectable in this manner has to inherit from a certain CRTP base. (Or, possibly, contain some kind of macro.)

  • You get a list of all parents, including indirect ones.

Run on gcc.godbolt.org

#include <cstddef>
#include <iostream>
#include <typeindex>
#include <utility>

template <typename T>
struct tag
{
    using type = T;
};

template <typename ...P>
struct type_list
{
    inline static constexpr std::size_t size = sizeof...(P);
};

namespace impl
{
    constexpr void adl_ViewBase() {} // A dummy ADL target.

    template <typename D, std::size_t I>
    struct BaseViewer
    {
        #if defined(__GNUC__) && !defined(__clang__)
        #pragma GCC diagnostic push
        #pragma GCC diagnostic ignored "-Wnon-template-friend"
        #endif
        friend constexpr auto adl_ViewBase(BaseViewer);
        #if defined(__GNUC__) && !defined(__clang__)
        #pragma GCC diagnostic pop
        #endif
    };

    template <typename D, std::size_t I, typename B>
    struct BaseWriter
    {
        friend constexpr auto adl_ViewBase(BaseViewer<D, I>) {return tag<B>{};}
    };

    template <typename D, typename Unique, std::size_t I = 0, typename = void>
    struct NumBases : std::integral_constant<std::size_t, I> {};

    template <typename D, typename Unique, std::size_t I>
    struct NumBases<D, Unique, I, decltype(adl_ViewBase(BaseViewer<D, I>{}), void())> : std::integral_constant<std::size_t, NumBases<D, Unique, I+1, void>::value> {};

    template <typename D, typename B>
    struct BaseInserter : BaseWriter<D, NumBases<D, B>::value, B> {};

    template <typename T>
    constexpr void adl_RegisterBases(void *) {} // A dummy ADL target.

    template <typename T>
    struct RegisterBases : decltype(adl_RegisterBases<T>((T *)nullptr), tag<void>())
    {};

    template <typename T, typename I>
    struct BaseListLow {};

    template <typename T, std::size_t ...I>
    struct BaseListLow<T, std::index_sequence<I...>>
    {
        static constexpr type_list<decltype(adl_ViewBase(BaseViewer<T, I>{}))...> helper() {}
        using type = decltype(helper());
    };

    template <typename T>
    struct BaseList : BaseListLow<T, std::make_index_sequence<(impl::RegisterBases<T>{}, NumBases<T, void>::value)>> {};
}

template <typename T>
using base_list = typename impl::BaseList<T>::type;

template <typename T>
struct Base
{
    template <
        typename D,
        std::enable_if_t<std::is_base_of_v<T, D>, std::nullptr_t> = nullptr,
        typename impl::BaseInserter<D, T>::nonExistent = nullptr
    >
    friend constexpr void adl_RegisterBases(void *) {}
};


struct A : Base<A> {};
struct B : Base<B>, A {};
struct C : Base<C> {};
struct D : Base<D>, B, C {};

template <typename T>
void printType()
{
    #ifndef _MSC_VER
    std::cout << __PRETTY_FUNCTION__ << '\n';
    #else
    std::cout << __FUNCSIG__ << '\n';
    #endif
};

int main()
{
    static_assert( base_list<D>::size == 4 );
    printType<base_list<D>>(); // typeList<tag<A>, tag<B>, tag<C>, tag<D>>, order may vary
}

Here's what's going on:

  • You use stateful template metaprogramming to create a list of types, which you can append types to by instantiating a specific template.
  • Using a CRTP base, you add a friend function to each class that needs to be detectable in this manner. Those functions are made uncallable with SFINAE, but merely considering them during overload resolution instantiates the template that appends the corresponding base class to the list.
  • You invoke this overloaded function with ADL, and since overloads from all bases are considered and are attempted to be instantiated, you get a list of bases.
HolyBlackCat
  • 63,700
  • 7
  • 105
  • 170