Search code examples
c++c++11templatesvariadic-templatesargument-unpacking

How can I unpack variadic template, so as to initialize respective members?


I am new to variadic templates and packed arguments and all. I want to have a "entity component system" in my program, and while trying to add component to the entity, I came to realize that my attempt has a major flaw.

Anyways here is my failed attempt.

struct A{
    float x;
    float y;
    
    A(float _x, float _y):x(_x), y(_y){}
};

struct B{
    float x;
    float y;
    float z;
    
    B(float _x, float _y, float _z):x(_x), y(_y), z(_z){}
};

struct Entity{
    A* a = NULL;
    B* b = NULL;
    
    Entity(){}
    
    template<typename T, typename... args>
    void Add(args... _args){
        if(typeid(T) == typeid(A))
            a = new A(_args...);
        else if(typeid(T) == typeid(B))
            b = new B(_args...);
        else
            throw("Invalid component added");
    }
};

And implementation looks like this..

Entity ent;
ent.Add<A>(12.24f, 123.246f);
ent.Add<B>(1.f, 1.2f, 1.23f);

I want the implementation to work somehow.. what has to be changed for it??


Solution

  • You can use the following (all in compile time):

    1. std::is_same to check if 2 types are the same (requires #include <type_traits>).
    2. if constexpr to branch based on it (c++17 onwards).
    3. static_assert if you want to get a compilation error if Add is instatiated with types other than A or B (see note about it below).

    Your modified code:

    #include <type_traits>
    
    struct A {
        float x;
        float y;
    
        A(float _x, float _y) :x(_x), y(_y) {}
    };
    
    struct B {
        float x;
        float y;
        float z;
    
        B(float _x, float _y, float _z) :x(_x), y(_y), z(_z) {}
    };
    
    struct Entity {
        A* a = nullptr;
        B* b = nullptr;
    
        Entity() {}
    
        template<typename T, typename... args>
        void Add(args... _args) {
            if constexpr (std::is_same_v<T, A>)
                a = new A(_args...);
            else if constexpr (std::is_same_v<T, B>)
                b = new B(_args...);
            else
                //static_assert(!sizeof(T), "T must be A or B");  // Enable this line to get a compilation error in this case
                throw("Invalid component added");
        }
    };
    
    int main() {
        Entity ent;
        ent.Add<A>(12.24f, 123.246f);
        ent.Add<B>(1.f, 1.2f, 1.23f);
    }
    

    Notes:

    1. I used the standard nullptr which is recommended instead of NULL.
    2. Using a simple static_assert(false, ...) is not guaranteed to work for the reason mentioned in the article in @RemyLebeau's comment (How can I create a type-dependent expression that is always false?).
      One of the solution suggested in that article is to use static_assert(!sizeof(T), ...) assuming sizeof is never 0. The article also suggests a slightly more complicated solution that avoids this assumption.

    Live demo - Godbolt