Check if class is derived from a specific class (compile, runtime both answers available)

32,400

Solution 1

I have some remarks on the proposed compile-time x runtime solutions. In addition to when they are evaluated, is_base_of and dynamic_cast have different requirements and their answers can be different.

(1) First of all (as pointed out by others) to use dynamic_cast, base and derived classes must be polymorphic (must have at least one virtual method). is_base_of doesn't require the types to be polymorphic.

(2) The operands of is_base_of are both types whereas dynamic_cast needs a type (inside < >) and an object (inside ( )).

(3) dynamic_cast and is_base_of can give different answers (or one can compile while the other doesn't) depending on the type of inheritance (public vs protected or private). For instance consider:

struct B { virtual ~B() {} }; // polymorphic, so it can be used in a dynamic_cast
struct D1 : public B {};      // polymorphic by (public)  inheritance
struct D2 : private B {};     // polymorphic by (private) inheritance

D1 d1;
D2 d2;

We have

static_assert(std::is_base_of<B, D1>::value, "");
static_assert(std::is_base_of<B, D2>::value, "");

assert(dynamic_cast<B*>(&d1));
assert(!dynamic_cast<B*>(&d2)); // Notice the negation.

Actually the last line yields a compiler error in GCC (error: 'B' is an inaccessible base of 'D2'). VS2010 does compile it (yielding just a warning similar to GCC's error message).

(4) The requirements on the classes being polymorphic can be relaxed through an exception handling trick. Consider:

struct B { };             // not polymorphic
struct D1 : public B {};  // not polymorphic
struct D2 : private B {}; // not polymorphic

D1 d1;
D2 d2;

template <typename B, typename D>
const B* is_unambiguous_public_base_of(const D* obj) {
    try {
        throw obj;
    }
    catch (const B* pb) {
        return pb;
    }
    catch (...) {
    }
    return nullptr;
}

Then we have

static_assert(std::is_base_of<B, D1>::value, "");
static_assert(std::is_base_of<B, D2>::value, "");

assert((is_unambiguous_public_base_of<B>(&d1)));
assert(!(is_unambiguous_public_base_of<B>(&d2))); // Notice the negation.

It's worth mentionning that is_unambiguous_public_base_of is far slower than dynamic_cast and (this became more obvious after the renaming mentioned in the update below) always returns a nullptr for downcasts:

B* b1 = &d1;
assert(dynamic_cast<D1*>(b1));        // Requires D1 and B to be polymorphic.
assert(!(is_unambiguous_public_base_of<D1>(b1))); // Notice the negation.

A bit outdated reference on this trick is available in the following links:

Part 1, Part 2 and code

Disclaimer: the implementation of is_unambiguous_public_base_of above is just a draft to make the point and it doesn't handle const and volatile qualifications properly.

Update: In a previous version of this post is_unambiguous_public_base_of was named my_dynamic_cast and this was a source of confusion. So I renamed it to a more meaningful name. (Thanks to Jan Herrmann.)

Solution 2

You could use dynamic_cast.

if (dynamic_cast<DerivedClass*>(ptr)) {
    std::cout << "Class is derived from DerivedClass.";
}

Solution 3

Check if class is derived from a specific class (compile time)

You can use std::is_base_of:

#include <type_traits>

....

const bool isBase = std::is_base_of<base, TheOtherClass>::value;

isBase is true if TheOtherClass is derived from base.

Solution 4

I think the answer to this question is very difficult. Of course there is std::is_base_of and dynamic_cast. Both provide you with some very limited information. The third option is function overloading. With all of those techniques you can choose a special path in your code which should be executed.

std::is_base_of can be interpreted in a boolean context and it is derived from either std::true_type or std::false_type. This fact makes it possible to use it as paramter for a function and use compile time polymorphism via function overloading. This first example shows how to use it in a boolean context, but you don't have any further specific type information. So compilation will fail in most circumstances (see here for a further description):

template<class T>
void do_it1(T const& t) {
  if (std::is_base_of<T,derived1>::value) {
    // we have a derived1 
  } else {
    // we have a base 
  }
}

The second version is simple function overloading. Here compile time polymorphism is used but all runtime type information is lost (except virtual functions and dynamic_cast are used):

void do_it2(Base const& b) {
  // we have a base your algorithm for base here
}
void do_it2(Derived2 const& d) {
  // Derived algorithm here
}

Now the third version combines both:

template<class T>
void do_it3_impl(T const& t, std::true_type) {
  // here t will be of a type derived from derived1
}

template<class T>
void do_it3_impl(T const& t,std::false_type) {
  // here t will be of type not derived from derived1
}

template<class T>
void do_it_3(T const& t) {
  do_it3_impl(t, std::is_base_of<T,derived1>()); // here we forward to our impl
}

The third variant is normaly used for header only libraries which don't use runtime poylmorphism (search for std::advance for an excample).

Now to runtime polymorphism. Here you have the dynaminc_cast variant:

void do_it4(Base const* ptr)
if (derived1 const* obj = dynamic_cast<derived*>(ptr)) {
  // here we have obj with type derived1*
} else {
  // here we have only base
}

If this variant is not fast enough you can implement your onw cast to derived1:

class derived1;
class base {
  // as above
public:
  virtual derived1 const*  to_derived1() const {
    return 0;
  }
};

class derived1 
   : public base 
{
  // ...
  virtual derived1 const*  to_derived1() const {
    return this;
  }
};

void do_it5(Base const* ptr)
if (derived1 const* obj = ptr->to_derived1() {
  // here we have obj with type derived1*
} else {
  // here we have only base
}

This is fast but it scales very well for only very few (approximately 1) derived classes.

Last but not least you should think about your class design and deside what methods to make virtual and implement in base, derived1 or other classes. You should definitly look for strategy pattern.

Share:
32,400
khajvah
Author by

khajvah

A bored web developer. Currently interested in: OS development Mathematics (though I don't have much knowledge in this subject) (maybe) Statistics What I hate: Corruption Email: [email protected]

Updated on February 05, 2022

Comments

  • khajvah
    khajvah over 2 years

    It is easier to explain on an example so,

    class base {
    //....
    }
    
    class derived1 : public base {
    //...
    }
    

    In my library, there is a pointer of base class. The user of the library have to make classes derived from either base or derived1 and assign pointer to that class.

    How can I check what class is user-defined class derived from?