7

I have the following hierarchy:

class base
{
public:
   virtual ~base(){}
   virtual void foo() {}
};

template <typename T>
class derived1 : public base
{
   virtual void foo() {};
};

template <typename T>
class derived2 : public base
{
   virtual void foo() {};
};

Now given a pointer to base, I'd like to find out if the underlying is either derived1 or derived2. The problem is that both derived1 and derived2 can be specialised on many different types, using dynamic_cast to test for a down cast requires the template type to be know. I've ended up with messy, unmaintable and incomplete bit of code:

base* b = new derived1<int>();

if (dynamic_cast<derived1<int>*> ||
    dynamic_cast<derived1<unsigned int>*> ||
    dynamic_cast<derived1<double>*>)
  std::cout << "is derived1";
else if (dynamic_cast<derived2<int>*> ||
    dynamic_cast<derived2<unsigned int>*> ||
    dynamic_cast<derived2<double>*>)
  std::cout << "is derived2";

Is there a better way, that can handle any type specialization?

Xander Tulip
  • 1,438
  • 2
  • 17
  • 32
  • 2
    What do you intend to do once you know that the type is a specialization of one of the derived templates? – James McNellis Mar 05 '12 at 02:20
  • @James: call a particular function for each one - btw there's more than derived1 and derived2 – Xander Tulip Mar 05 '12 at 02:23
  • 6
    Conditional code that needs to dynamically test the concrete type of a base class pointer is a bad code smell. There's probably a way to change your design to avoid this. – Emile Cormier Mar 05 '12 at 02:24
  • 1
    @Emile: what is exactly bad about it? – Xander Tulip Mar 05 '12 at 02:26
  • Look at the External Links at the botton of the Downcasting wikipedia article (http://en.wikipedia.org/wiki/Downcasting). The first 3 of the referenced articles criticize downcasting. I doubt I could argue the point better than they can. – Emile Cormier Mar 05 '12 at 02:31
  • 3
    @XanderTulip: OK, here's a question. If you're giving some function a `base`, why does it *need* to call a function that is only on the `derived1` class? Either the function takes a `derived1` or it takes a `base`. If you find yourself having to do this, you need to re-evaluate your class hierarchy design. In short, you've gotten yourself into a bad design situation, and you need to find a way to get out of it. To help you do that, we need to know what your code is actually trying to accomplish. – Nicol Bolas Mar 05 '12 at 02:46
  • The destructor in `Base` needs to be virtual, or you'll get resource/memory leaks (because the derived classes' destructors will never get called). – Emile Cormier Mar 05 '12 at 03:10
  • @Emile: it was a typo fixed it up thanks for that. – Xander Tulip Mar 05 '12 at 03:58

4 Answers4

9

Move the logic which depends on the type into the type.

Instead of:

if (dynamic_cast<derived1<int>*>(b) ||
    dynamic_cast<derived1<unsigned int>*>(b) ||
    dynamic_cast<derived1<double>*>(b))
  std::cout << "is derived1";
else if (dynamic_cast<derived2<int>*>(b) ||
    dynamic_cast<derived2<unsigned int>*>(b) ||
    dynamic_cast<derived2<double>*>(b))
  std::cout << "is derived2";

add a virtual print_name() const function to base, and then do:

void example() {
    std::unique_ptr<base> b(new derived1<int>());
    b->print_name();
}
class base
{
public:
   ~base(){}
   virtual void foo() {}
   virtual void print_name() const = 0;
};

template <typename T>
class derived1 : public base
{
   virtual void foo() {}
   virtual void print_name() const {
       std::cout << "is derived1";
   }
};

template <typename T>
class derived2 : public base
{
   virtual void foo() {}
   virtual void print_name() const {
       std::cout << "is derived2";
   }
};
Mankarse
  • 39,818
  • 11
  • 97
  • 141
7

Insert a non-templated class inbetween base and derived1 or derived2:

class base
{
public:
   virtual ~base() {}  // **NOTE** Should be virtual
   virtual void foo() {}
};

class derived1_base : public base
{
};

template <typename T>
class derived1 : public derived1_base
{
public:
   virtual void foo() {}
};

class derived2_base : public base
{
};

template <typename T>
class derived2 : public derived2_base
{
public:
   virtual void foo() {}
};

In a comment, you mentioned:

[I want to] call a particular function for each one - btw there's more than derived1 and derived2

Add that (virtual) function to derived1_base, and you don't even need to know T anymore.

if (dynamic_cast<derived1_base*>(foo))
{
  std::cout << "is derived1";
  dynamic_cast<derived1_base*>(foo)->specific_derived1_function();
}
else if (dynamic_cast<derived2_base*>(foo))
{
  std::cout << "is derived2";
  dynamic_cast<derived2_base*>(foo)->specific_derived2_function();
}

NOTE: I consider a list of dynamic_cast<> a code smell, and I urge you to rethink your approach.

Sjoerd
  • 6,837
  • 31
  • 44
3

You could add a virtual method to do a meta-type check of some kind:

class base
{
public:
    ~base(){}
    virtual void foo() {}
    virtual bool isa(const char* type_to_test){
          return strcmp(type_to_test,"base")==0;}
};

template <typename T>
class derived1 : public base
{
   virtual void foo() {};
   virtual bool isa(const char* type_to_test){
   return strcmp(type_to_test,"derived1")==0;}
};
tmpearce
  • 12,523
  • 4
  • 42
  • 60
2

Solution 1: add one more virtual function:

enum DerivedType
{
    One,
    Two,
    ...
};

class base 
{ 
public: 
   ~base(){} 
   virtual void foo() {}
   virtual DerivedType GetType() = 0;
}; 

template <typename T> 
class derived1 : public base 
{ 
   virtual void foo() {};
   virtual DerivedType GetType() { return One; }
}; 

template <typename T> 
class derived2 : public base 
{ 
   virtual void foo() {};
    virtual DerivedType GetType() { return Two; }
}; 

Solution 2: using tag classes:

class Base
{
public:
    virtual ~Base() { }
};

class Derived1Tag
{ };

class Derived2Tag
{ };

template <class T>
class Derived1 : public Base, public Derived1Tag
{ };

template <class T>
class Derived2 : public Base, public Derived2Tag
{ };


int main(int argc, char** argv)
{
    Derived1<int> d1;
    Derived2<int> d2;

    cout << dynamic_cast<Derived1Tag*>((Base*)&d1) << endl;
    cout << dynamic_cast<Derived1Tag*>((Base*)&d2) << endl;

    return 0;
}
J.N.
  • 8,203
  • 3
  • 29
  • 39
  • I must say that @EmileCormier is right and that this **probably** reflects a bad design choice somewhere. If the function you want to run depends on the type, then it probably should be a virtual function from the start. – J.N. Mar 05 '12 at 02:47