13

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?

Cassio Neri
  • 19,583
  • 7
  • 46
  • 68
khajvah
  • 4,889
  • 9
  • 41
  • 63

6 Answers6

16

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.)

Cassio Neri
  • 19,583
  • 7
  • 46
  • 68
  • Your part 4 is *WRONG* you are casting from a derived to a public base. This works, its only pointer conversion. But you don't cast from a pointer of type `B*` which points to a `D1` to a `D1*`. – Jan Herrmann Aug 07 '13 at 11:20
  • @Jan Herrmann: What exactly is wrong? Yes, using `my_dynamic_cast` to cast from `D1*` to `B*` yields a non NULL ptr (as I said, `assert((my_dynamic_cast(&d1)));` passes). Using it from `D2*` to `B*` yields a NULL ptr (as I said, `assert(!(my_dynamic_cast(&d2)));` passes). Using it for downcasts (contrarilly to `dynamic_cast`) ALWAYS returns a NULL ptr (as I said, `assert(!(my_dynamic_cast(b1)));` passes). I never said that `is_base_of`, `dynamic_cast` and `my_dynamic_cast` can replace one another. That's the point of my post: to show their differences in semantics and requirements. – Cassio Neri Aug 07 '13 at 12:05
  • look at http://coliru.stacked-crooked.com/view?id=d71f93429fd0abda9911e541a267ca90-f674c1a6d04c632b71a62362c0ccfc51 I added lines 28 and 29, this is what I would expect from a replacement of `dynamic_cast`. So your part 4 is useless. It does something which can be achieved with simple function overloading (which would result in the same as `static_cast`). So what is its purpose? – Jan Herrmann Aug 07 '13 at 12:14
  • Again, I never said that `my_dynamic_cast` is a replacement for `dynamic_cast` (I agree that I could have chosen a better name though). In addition your test `assert(my_dynamic_cast(b1));` is exactly the opposite of mine: `assert(!(my_dynamic_cast(b1))); // Notice the negation.` (See above). – Cassio Neri Aug 07 '13 at 12:19
  • For use cases, read the reference that I gave in the post. See also slide 27 of this [presentation](http://tinyurl.com/m8f7nsy) by Andrei Alexandrescu where he uses a similar trick. – Cassio Neri Aug 07 '13 at 12:26
  • Ok I didn't read your post up to the end (I didn't read the last code sample). First I thought I learned something new to exceptions (I read your article, too). But now I know you are right. All your asserts are true. Nevertheless part 4 does contain no code which can not be replaced with `static_cast`. So I think your post suggest something which can not be achieved with C++ or it makes something very complicated with exceptions which can be made easy without. (Now I will look at this viedo.) – Jan Herrmann Aug 07 '13 at 12:30
  • 1
    The confusion is mainly due to the name that I have chosen: `my_dynamic_cast`. So I'll update the post to rename it to, say, `my_is_base_of` ;-). Thanks for the discussion anyway. – Cassio Neri Aug 07 '13 at 12:41
  • Updated again from `is_public_base_of` to `is_unambiguous_public_base_of`. :) For example, if `D : public B,C` and `B : public A` and `C: public A`, then `is_public_base_of(new D)` returns `false`. (See the [dreaded diamond](http://www.parashift.com/c++-faq/mi-diamond.html).) – Quuxplusone Dec 07 '13 at 00:43
3

You could use dynamic_cast.

if (dynamic_cast<DerivedClass*>(ptr)) {
    std::cout << "Class is derived from DerivedClass.";
}
sashoalm
  • 75,001
  • 122
  • 434
  • 781
1

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.

juanchopanza
  • 223,364
  • 34
  • 402
  • 480
1

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.

Community
  • 1
  • 1
Jan Herrmann
  • 2,717
  • 17
  • 21
0

The compiler will only accept pointers to classes derived from your base class if your library functions takes pointers to the base class. My answer is with a classical approach type safety will handle it. To my experience that kind of type checking is enough. Having 25 years experience in the industry I question the need to do this check. Maybe such a fundamental question is not welcome? I would like to see the justification to having the need to do this kind of upcast. I never have to do that. The opposite, i.e. a down cast I need quite frequently.

0

You can do this in several ways. The most common ones as the others have pointed out are dynamic_cast<> and std::is_base_of. The latter is used at compile time, while dynamic_cast<> can be used at runtime. HOWEVER, dynamic_cast<> will only work if your source class is polymorphic (i.e. has at least one virtual function - it can be a method or its destructor). If not, the compiler will trigger an error.

Iosif Murariu
  • 2,019
  • 1
  • 21
  • 27