2

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??

Seon Il
  • 43
  • 8
  • 2
    You want use [`std:: is_same`](https://en.cppreference.com/w/cpp/types/is_same) and `if constexpr`. – 273K May 12 '23 at 04:09
  • You can massage this into a compilable form but it doesn't make a lot of sense. Separate AddA and AddB functions do the job better in every way. – n. m. could be an AI May 12 '23 at 04:54
  • If this is part of actual code, just use `std::variant` instead. The constructor can be called with a `std::in_place_type` instance as 1st parameter. You can use assignment, if after construction anything needs to be changed. – Red.Wave May 12 '23 at 07:36

1 Answers1

2

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

wohlstad
  • 12,661
  • 10
  • 26
  • 39
  • Note that `static_assert(false)` doesn't work as expected in this case. It will fail as soon as it is parsed by the compiler. To delay the error until the template is instantiated, you need to use a [type-dependent expression that is always false](https://devblogs.microsoft.com/oldnewthing/20200311-00/?p=103553). – Remy Lebeau May 12 '23 at 05:35
  • @RemyLebeau interesting link. However - gcc seems to compile it OK (enable the static_assert in my Godbolt link and see). It is a bug in gcc ? Or the issue in the article is resticted to lambdas (which is what is mentioned there) ? – wohlstad May 12 '23 at 05:50
  • 1
    The issue is not related to lambdas. When I run your Godbolt example using gcc 11.x or 12.x (or 10.x with `-std=c++17` specified), the `static_assert` fails immediately as expected. The code runs OK in gcc 13.x, though. So, either this is a bug in gcc 13.x, or they intentionally changed the behavior. – Remy Lebeau May 12 '23 at 05:59
  • Using `static_assert` works fine in all compilers, you just have to use it *correctly*, which your example is not (but gcc is letting you get away with it) – Remy Lebeau May 12 '23 at 06:09
  • @RemyLebeau you are right. Updated my answer. – wohlstad May 12 '23 at 06:25