0

Assume there is a template class that is an abstract interface to keep track a series of data.

template<typename T> class SeriesData { virtual T valueAt(int i) const = 0; }; 

I decide to use vector to store the data so I inherits from it, I also add a function to add value to series.

template<typename T> class MySeriesData : public SeriesData<T> { T valueAt(int i) const {return values_[i];} void add(const T& value) {values_.push_back(value);} private: std::vector<T> values_; }; 

The class that is going to use the series of data.

template<typename T> class AT { public: T data() const {return data_;} void setData(SeriesData<T>* data) {delete data_; data_ = data;} private: SeriesData<T> *data_; }; class IT : public AT<int> { }; class ST : public AT<string> { }; 

There is also a class that implements some common logic:

class A { public: A(){} virtual ~A() {} protected: void common() {cout << "A common" << endl;} }; 

And there are two classes, each of which inherits from IT and ST, and both of which need to inherit from A. I want to have them inherit from A because there are so much common logic.

class Numbers : public A , public IT { }; class Strings : public A , public ST { }; 

In the main logic, I will use a pointer in type of A to dynamically create the instance of either Numbers or Strings based on the type client wants.

A* item; if(type == NUMBER) item = new Numbers(); else if(type == STRING) item = new Strings(); 

I can just use item to do those common things, which is good.

item->common(); 

However, for those logic that are related to AT. I would need to check the real type and then do cast.

item->common(); if(type == NUMBER) { IT* itemNumber = static_cast<Numbers*>(item); itemNumber->setData(new MySeries<int>()); } else if(type == STRING) { ST* itemString = static_cast<Strings*>(item); itemString->setData(new MySeries<string>()); } ..... if(type == NUMBER) { //Have to cast from A to the a type of AT first Numbers* itemNumber = static_cast<Numbers*>(item); //Another conversion MySeries<int>* numberValues = (MySeries<int>*)itemNumber->data(); numberValues->add(1); } else if(type == STRING) { Strings* itemString = static_cast<Strings*>(item); MySeries<string>* stringValues = (MySeries<string>*)itemString->data(); stringValues->add("1"); } 

This is annoying when the code is full of these if...else or switch...case, especially when there are more and more types.

Is it possible to cast the type more smartly? It will be nice if I can always (or in most of cases) use pointer of A to do all the stuffs.

13
  • If you have to downcast, there's a large possibility there's a better "more OOP" way of doing things. At the very least, you're breaking the Liskov Substitution Principle. Commented Jan 16, 2019 at 20:14
  • 1
    If you, for some reason, cannot create a common interface type, there is always the possibility of using type erasure. Commented Jan 16, 2019 at 20:16
  • Note that item = static_cast<B*>(item) doesn't work like you think. Since item is statically typed as an A*, assigning the casted pointer back to item just ends up giving you the same pointer that you started with. You need something more like this instead: if(type == NUMBER) { B *b = static_cast<B*>(item); b->get(); } else if(type == STRING) { C *c = static_cast<C*>(item); c->get(); } and so on. Commented Jan 16, 2019 at 21:05
  • 1
    @curiousguy. hmmm..... Good question. I think my original post was wrong. It is supposed to return int for B::get() and string for C::get(), which means the item->get() could return different type. Commented Jan 16, 2019 at 22:33
  • 2
    @curiousguy In terms of logic, there isn't. However, if he is not able to create a base type (i.e. the source code of a type is not accessible, like in 3rd party code), type erasure is a good alternative as it doesn't rely on a common type hierarchy. If however, those types have nothing in common (i.e. identical function signatures) type erasure won't be that helpful aswell. Commented Jan 17, 2019 at 16:58

1 Answer 1

1

You can wrap the type-dependent operations in helper functions that are stored in lookup tables keyed by type, for example:

std::map<int, A*(*)()> makeAMap; makeAMap[NUMBER] = []() -> A* { return new B; }; makeAMap[STRING] = []() -> A* { return new C; }; ... std::map<int, void(*)(A*)> getFuncMap; getFuncMap[NUMBER] = [](A *item){ static_cast<B*>(item)->get(); }; getFuncMap[STRING] = [](A *item){ static_cast<C*>(item)->get(); }; ... A* item = makeAMap[type](); ... item->common(); getFuncMap[type](item); 

Live Demo

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.