Search code examples
c++classc++11encapsulation

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


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?


Solution

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