user2023370
user2023370

Reputation: 11037

Obtaining the runtime type of a derived template instantiation

I can use say typeid or dynamic_cast to switch between derived base class instances. For example:

struct B { virtual ~B() {} };

struct D1 : B {};
struct D2 : B {};

void doit(B &b) {
  if      (dynamic_cast<D1 *>(&b)) foo1();
  else if (dynamic_cast<D2 *>(&b)) foo2();
}

How can I continue in this vein when the derived type is a template instantiation? For example, how could I extend the doit function above to handle four cases; perhaps using the DT1 and DT2 classes below, instantiated with arbitrary types?

template <typename T>
struct DT1 : B {};

template <typename T>
struct DT2 : B {};

Upvotes: 2

Views: 65

Answers (1)

Chris Drew
Chris Drew

Reputation: 15334

Instead of hardcoding the list of derived classes in the doit function you could maintain some sort of registry of foo functions to call for each type. In the simplest form this could just be a vector of std::function<void(B&)> and you just loop through and call each of them. Each std::function would be responsible for checking if the type matches:

auto& doitRegistry(){
  static std::vector<std::function<void(B&)>> registry;
  return registry;
}

void doit(B &b) {
  for (auto& f : doitRegistry()) {
    f(b);
  }
}  

If you wanted to be smarter the registry could be something like a std::unordered_map<std::type_index, std::function<void(B&)>> so that you don't have to loop through the whole registry but as long as you don't have a huge number of derived classes it probably doesn't matter much.

Then you just have to register each type of derived class in the registry. You can create a helper class that registers a function in the registry in its constructor:

struct DoItRegistration {
  DoItRegistration(std::function<void(B&)> foo) {
     doitRegistry().push_back(std::move(foo));
  }
};

And create a static instance of this class per derived class:

template <typename T>
struct DT1 : B {
  DT1() {
        registerWithDoIt();
  }
  static DoItRegistration& registerWithDoIt() {
      static DoItRegistration registration([](B &b){
        if (dynamic_cast<DT1<T>*>(&b)){
         foo1();
        }
      }); 
      return registration;
  }
};

So that on first use of a derived class it registers itself with the doit registry and will be called when needed:

int main() {
    DT1<double> d1;
    DT1<int> d2;
    DT2<double> d3;
    DT1<double> dupeType;

    doit(d3);  // calls foo2()
}

Live demo.

Upvotes: 1

Related Questions