This solution involves comparing typeid s. TEImpl knows its own type, so it can check the passed typeid on its own.
The problem is that this method does not work when you add inheritance, so I also use template metaprogramming to check if typedef super exists, in which case it will recursively check its parent class.
struct TEBase { virtual ~TEBase() {} virtual bool is_type(const type_info& ti) = 0; }; template <typename T> struct TEImpl : public TEBase { bool is_type(const type_info& ti) { return is_type_impl<T>(ti); } template <typename Haystack> static bool is_type_impl(const type_info& ti) { return is_type_super<Haystack>(ti, nullptr); } template <typename Haystack> static bool is_type_super(const type_info& ti, typename Haystack::super*) { if(typeid( Haystack ) == ti) return true; return is_type_impl<typename Haystack::super>(ti); } template <typename Haystack> static bool is_type_super(const type_info& ti, ...) { return typeid(Haystack) == ti; } }; template <typename T> bool is_derived_from(TEBase* p) { return p->is_type(typeid( T )); }
For this, Bar must be redefined as:
struct Bar : public Foo { typedef Foo super; };
This should be efficient enough, but it’s obvious that it’s not intrusive, since typedef super is required in the target class whenever inheritance is used. typedef super should also be publicly available, which contradicts what many consider the recommended practice of placing your typedef super in your private section.
It also has nothing to do with multiple inheritance at all.
Update: This decision can be made to make it general and non-intrusive.
Make it shared
typedef super excellent because it is idiomatic and already used in many classes, but does not allow multiple inheritance. To do this, we will need to replace it with a type that can store several types, for example, a tuple.
If Bar was rewritten as:
struct Bar : public Foo, public Baz { typedef tuple<Foo, Baz> supers; };
we could support this form of declaration by adding the following code to TEImpl:
template <typename Haystack> static bool is_type_impl(const type_info& ti) { // Redefined to call is_type_supers instead of is_type_super return is_type_supers<Haystack>(ti, nullptr); } template <typename Haystack> static bool is_type_supers(const type_info& ti, typename Haystack::supers*) { return IsTypeTuple<typename Haystack::supers, tuple_size<typename Haystack::supers>::value>::match(ti); } template <typename Haystack> static bool is_type_supers(const type_info& ti, ...) { return is_type_super<Haystack>(ti, nullptr); } template <typename Haystack, size_t N> struct IsTypeTuple { static bool match(const type_info& ti) { if(is_type_impl<typename tuple_element< N-1, Haystack >::type>( ti )) return true; return IsTypeTuple<Haystack, N-1>::match(ti); } }; template <typename Haystack> struct IsTypeTuple<Haystack, 0> { static bool match(const type_info& ti) { return false; } };
Make it non-intrusive
Now we have a solution, effective and general, but it is still intrusive, so it will not support classes that cannot be changed.
To support this, we need a way to declare the inheritance of objects from outside the class. For Foo we could do something like this:
template <> struct ClassHierarchy<Bar> { typedef tuple<Foo, Baz> supers; };
To maintain this style, we first need a non-specialized form of ClassHierarchy, which we define as follows:
template <typename T> struct ClassHierarchy { typedef bool undefined; };
We will use the presence of undefined to determine if this class was specialized.
Now we need to add some more functions to TEImpl. We will continue to use most of the code before, but now we will also support reading type data from ClassHierarchy .
template <typename Haystack> static bool is_type_impl(const type_info& ti) { // Redefined to call is_type_external instead of is_type_supers. return is_type_external<Haystack>(ti, nullptr); } template <typename Haystack> static bool is_type_external(const type_info& ti, typename ClassHierarchy<Haystack>::undefined*) { return is_type_supers<Haystack>(ti, nullptr); } template <typename Haystack> static bool is_type_external(const type_info& ti, ...) { return is_type_supers<ClassHierarchy< Haystack >>(ti, nullptr); } template <typename Haystack> struct ActualType { typedef Haystack type; }; template <typename Haystack> struct ActualType<ClassHierarchy< Haystack >> { typedef Haystack type; }; template <typename Haystack> static bool is_type_super(const type_info& ti, ...) { // Redefined to reference ActualType return typeid(typename ActualType<Haystack>::type) == ti; }
And now we have a solution that is effective, general and non-intrusive.
Future decision
This solution satisfies the criteria, but still a little annoying to need to document the class hierarchy explicitly. The compiler already knows everything about the class hierarchy, so it’s a shame that we should do this work.
Proposed solution to this problem: N2965: Type and base classes that were implemented in GCC . This document defines the direct_bases class, which is almost identical to our ClassHierarchy class, except that its only type element is guaranteed to be a tuple, such as supers , and the class is fully generated by the compiler.
So, now we need to write a small template to make it work, but if N2965 is accepted, we can get rid of the template and make TEImpl much shorter.
Special thanks to S. Kerrek and Jan Herrmann. This answer inspired their comments.