2

I am working on a simple game engine that provides a base class for game objects that can be extended with subclasses for the specific game. I need to write a function that can take a file, parse object names from it, and instantiate the corresponding objects in the game; providing a mechanism for storing level data in files. I had hoped to use metaprogramming to create a function that allows the caller to pass in a variable number of data types and generates a function that searches for names corresponding to those types in a file. Its use would look something along the lines of this (using templates):

fileParseFunction<type1, type2 type3>("filename");

would generate a function equivalent to:

fileParseFunction(string filename)
{
    //code that opens file called "filename" and handles tokenizing/parsing

    if(token == "type1")
    {
        gameWorld.add(new type1());
    }
    elseif(token == "type2")
    {
        gameWorld.add(new type2());
    }
    elseif(token == "type3")
    {
        gameWorld.add(new type3());
    }

    //other code to finish loading the level 
}

Called with the parameter "filename". This should work for a variable number of types (3 in the example). I wrote some code to test the concept which involves generating a similar function. It uses templates to convert typename symbols to strings (this is needed for use in the comparisons in the function I eventually hope to write) and also variadic templates to generate a function that prints the names of all the types passed in as template parameters. Here it is:

#define TypeNameTemplate(a) template<> inline const char* typeName<a>(void) { return #a; }

template <typename T>
inline const char* typeName(void) { return "unknown"; }

TypeNameTemplate(int);
TypeNameTemplate(std::string);
TypeNameTemplate(double);
TypeNameTemplate(bool);
TypeNameTemplate(float);

/*template <>
inline const char* typeName<int>(void) { return "int"; }*/

template <typename T> inline void printtypes()
{
    std::cout << typeName<T>();
}

template <typename T, typename... Args> void printtypes()
{
    std::cout << typeName<T>() << std::endl;
    printtypes<Args...>();
}

using namespace std;

int main()
{
    //string a = typeName<int>();
    //print();
    printtypes<int, double, string, bool, float>();
    return 0;
}

printtypes() should generate a function equivalent to:

void printtypes()
{
    std::cout << typeName<int>();
    std::cout << typeName<std:string>();
    std::cout << typeName<double>();
    std::cout << typeName<bool>();
    std::cout << typeName<float>();
}

However, during compilation i get this error:

E:\C++ projects\templateTest\main.cpp:26:5: note: candidates are: E:\C++ projects\templateTest\main.cpp:18:35: note: void printtypes() [with T = float] E:\C++ projects\templateTest\main.cpp:23:46: note: void printtypes() [with T = float; Args = {}]

It appears that upon reaching the end up the variadic parameter pack recursively, the compiler does not know whether to call the template specialized on only one type with the last type in the pack, or the variadic template with the last type in the pack plus an empty parameter pack. Is what I'm attempting to do possible/practical in C++, and is there a way to let the compiler know that it should use the singe parameter template for the base/final case of the recursive call?

2 Answers 2

2

A simple solution is to add another explicit parameter to the second overload:

template <typename T, typename T2, typename... Args> void printtypes()
{
    std::cout << typeName<T>() << std::endl;
    printtypes<T2,Args...>();
}
Sign up to request clarification or add additional context in comments.

1 Comment

Your approach is likely the most simple. I deleted mine out of shame ;)
1

While this answer does not answer your specific question about variadic templates, I hope it does answer your underlying question about how to make your game engine extensible.

What you came up with by creating your fileParseFunction() is an implementation of the Factory Method pattern. This is the main part that makes it easy to turn stored data into real objects. Unfortunately, it violates the Open-Close Principle making it difficult to reach your end goal, extendability.

For example, in your code above, your factory function can parse "type1", "type2" and "type3" from your data file and generate objects of type1, type2 and type3 but adding more types would mean editing this function and adding a new else if for every new type you wish to add

You've already identified this as a problem and are trying to solve it using variadic templates. Unfortunately, if you extend the number of game objects into the twenties, thirties, or even hundreds of types, variadic templates will become cumbersome to use, if at all they are able to go that far.

A much simpler solution is to use the Abstract Factory pattern. This essentially shifts responsibility for creating game objects from your file parser's Factory Function, to a factory object. Whether this transfer of power goes to a single function, or a fully-fledged class is up to you. You could also templatise this factory to save on coding.

Each of your factories will have to register their existence with the file parser before the parser is called and extending the parser's capabilities will be as simple as creating a new factory and registering it with the parser.

A simple example would be:

class GameObjectAbstractFactory {
public:
    string registeredTypes() const{
        // cycle through hash table to return the names of registered factories
    }

    GameObjectFactory* getFactory(string factoryName){
        // return the registered factory, or nullptr if not registered
    }

    void registerFactory(string factoryName, GameObjectFactory*){
        // add the factory if it doesn't exist
    }

    static GameObjectAbstractFactory* instance(){
        // access to Singleton instance
    }

private:
    GameObjectAbstractFactory(); // enforces Singleton pattern

    Hash<string, GameObjectFactory*> registeredFactories;
};


// Interface class for generating concrete types, can be templatised, depending on implementation
class GameObjectFactory{
public:
    string name() = 0;
    GameObject *createObject() = 0;
};

This would alter your parsing function so that it becomes:

fileParseFunction(string filename)
{
    //code that opens file called "filename" and handles tokenizing/parsing

    GameObjectAbstractFactory *abstractFactory = GameObjectAbstractFactory::instance();

    GameObjectFactory *factory = abstractFactory.getFactory(token);
    if(factory != nullptr)
    {
        gameWorld.add(factory.createObject());
    }

    //other code to finish loading the level 
}

This would then make your fileParseFunction() compliant with the Open-Close Principle in that it could still generate new game objects as your engine is extended but the function itself will not have to be modified in order to do so.

There is a caveat with this pattern though: all the factories need to be registered with the abstract factory before they are needed, otherwise the required game object will not be able to be created.

As I mentioned in the beginning, this answer does not address your direct question about variadic templates but I hope this helps with the extendability of your game engine.

1 Comment

This looks like a much better solution, its very interesting. Looks like I have some reading on design patterns to do! Thank you for this suggestion.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.