0

I'm writing an Entity-Component-System framework for my game engine. Given that I intend for users to define their own structs and classes for components, I'm trying to make it as flexible as possible.

The problem is, I want to differentiate between objects that can be aggregate-initialized or initialized via their constructor. I know how to test if an object is constructible using the std::is_constructible type trait. However, when I attempt to use it, it fails to compile. Below is a short snippet of the relevant code. Note that there are other procedures before this that handle error checking and array-pool management, but I think those are irrelevant.

template <typename ... Fields>
void add_component(Fields ... params) {
    if(std::is_constructible<Component, Fields ...>::value) {
        std::cout << "Yes!\n";
        pool_[length_] = Component(params ...);
    }
    else {
        std::cout << "No\n";
        pool_[length_] = {params ...};
    }
    length_++;
}

I can clearly see that std::is_constructible is correctly identifying objects that can be constructed, but it still attempts to call the constructor at compile-time. For example:

struct Vec2D {
    float x, y;
};

If I attempt to add a Vec2D component to my entity, it gives me the following error:

error: no matching function for call to ‘Dynamo::Vec2D::Vec2D(float&, float&)’
  122 |                 pool_[length_] = Component(params ...);
      |                                  ^~~~~~~~~~~~~~~~~~~~~
In file included from src/main/../core/display.h:9,
                 from src/main/scene.h:9,
                 from src/main/engine.h:10,
                 from src/Dynamo.h:4,
                 from main.cpp:1:
src/main/../core/../util/vector.h:11:12: note: candidate: ‘Dynamo::Vec2D::Vec2D()’
   11 |     struct Vec2D {
      |            ^~~~~
src/main/../core/../util/vector.h:11:12: note:   candidate expects 0 arguments, 2 provided
src/main/../core/../util/vector.h:11:12: note: candidate: ‘constexpr Dynamo::Vec2D::Vec2D(const Dynamo::Vec2D&)’
src/main/../core/../util/vector.h:11:12: note:   candidate expects 1 argument, 2 provided
src/main/../core/../util/vector.h:11:12: note: candidate: ‘constexpr Dynamo::Vec2D::Vec2D(Dynamo::Vec2D&&)’
src/main/../core/../util/vector.h:11:12: note:   candidate expects 1 argument, 2 provided

I expect the method to aggregate-initialize the component, but it still attempts to call the constructor. What is wrong here? Perhaps there is something wrong with how I used templates?

Edit: I'm aware of if constexpr(), but I'm looking for a solution that could work with compiler versions before C++17. Is that still possible?

Anders Evensen
  • 579
  • 5
  • 15
SirBob
  • 33
  • 1
  • 7
  • 1
    [Use `if constexpr()`.](https://stackoverflow.com/questions/43434491/difference-between-if-constexpr-vs-if) An `if` still requires both branches to compile, even if the condition is a compile-time constant. An `if constexpr` actually discards the unreachable case. – HTNW Apr 29 '20 at 04:55
  • Is there a way to do this with compiler versions before C++17? – SirBob Apr 29 '20 at 04:58
  • 1
    I believe the old-school way would be to split the function into two overloads: `template std::enable_if_t::value> add_component(Fields... params) { /* true case */ } template std::enable_if_t<!std::is_constructible::value> add_component(Fields... params) { /* false case */ } ` – HTNW Apr 29 '20 at 05:02

0 Answers0