1

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;
}
Community
  • 1
  • 1
Zack
  • 6,232
  • 8
  • 38
  • 68
  • You already have a static `CharacterDescription` in all of the derived classes, don't you? Each of those constructors registers themselves to the `CharacterDescription::characterList`. – dalle Jan 10 '17 at 20:36
  • Yes, but that's for the non-templated version. When you make the Character and CharacterDescrption class templated, you can't add CharacterDescription to the derived class, because you don't know what T is. – Zack Jan 10 '17 at 20:46

0 Answers0