I would like to be able to instantiate a set of templated C++ objects based on selected command line parameters. The solution below (suggested by Is there a way to instantiate objects from a string holding their class name? ) would work if the types to be instantiated (i.e., the Character
classes) were not templated.
Also important: the code below allows each subclass of Character
to register itself. This means that users can add additional subclasses of Character
without having to touch any of the code in the library or explicitly manage a list of all possible subtypes. Question: Is there a way to get this same functionality for templated classes?
Users of the library choose the value of the template parameter when parsing the command line in main
. This means that CharacterDescription::make
and, as a result, the CharacterDescription
class must also be templated. At this point, I can't figure out how to let each Character
subclass "register" itself without maintaining a central list of all possible subclasses. The templated version of makeCharacters
below almost does what I want --- I just want to set up the code so that makeCharacters
does not maintain a list of all the subclasses.
(I suspect the answer will require some #define magic; but, I'm not seeing how to do it.)
Character.hpp
// Base Class
class Character {
};
// Descriptor for subclasses that may be instantiated.
// (i.e., contains the parameters used for the option parser
// as well as a pointer to a factory function)
class CharacterDescription {
typedef Character* (*CharacterFactory)(const char* params);
public:
const char* optionName;
const CharacterFactory factory;
CharacterDescription(const char* pOpt, const CharacterFactory pFactory) :
optionName(pOpt), factory(pFactory) {
characterList.push_back(*this);
}
template<typename T>
static Character *make(const char *params) {
return new T(params);
}
static vector<CharacterDescription> characterList;
};
Character.cpp
vector<CharacterDescription> CharacterDescription::characterList;
Animal.hpp
// Example subclass that may be requested on the command line.
class Animal : public Character {
public:
Animal(const char* /*params*/) {}
public:
static CharacterDescription description;
};
Animal.cpp
CharacterDescription Animal::description("animal", CharacterDescription::make<Animal>);
main.cpp
// Yes, this is gross, but it gets the point across
vector<Character*> makeCharacters(int argc, const char* argv[]){
vector<Character*> characters;
for (int i = 1;i < argc; i++) {
const char* current = argv[i];
auto foundItem = std::find_if(CharacterDescription::characterList.begin(),
CharacterDescription::characterList.end(),
[current](const CharacterDescription& item) {return strcmp(item.optionName,current)==0;});
if (foundItem != CharacterDescription::characterList.end() ) {
characters.push_back((*foundItem).factory(argv[i+1]));
}
}
return characters;
}
templated version of makeCharacters
template<typename T>
vector<Character<T>*> makeCharacters(int argc, const char* argv[]){
vector<CharacterDescription<T>> characterList;
//
// **Key question:** Is there a way to distribute these lines of code
// to the .hpp or .cpp file for each subclass?
//
characterList.push_back({"animal", CharacterDescription<T>::template make<Animal<T>>});
characterList.push_back({"tree", CharacterDescription<T>::template make<Tree<T>>});
characterList.push_back({"rock", CharacterDescription<T>::template make<Rock<T>>});
vector<Character<T>*> characters;
for (int i = 1;i < argc; i++) {
const char* current = argv[i];
auto foundItem = std::find_if(characterList.begin(),
characterList.end(),
[current](const CharacterDescription<T>& item) {return strcmp
(item.optionName,current)==0;});
if (foundItem != characterList.end() ) {
characters.push_back((*foundItem).factory(argv[i+1]));
}
}
return characters;
}