Home > other >  Unpacking a tuple to call a function templated with variadic arguments in a subclass implementation
Unpacking a tuple to call a function templated with variadic arguments in a subclass implementation

Time:08-09

I am in the midst of implementing an Entity Component System. I am running into issues when attempting to call a function templated with variadic arguments:

template <typename... Ts>
struct engine_system : engine_system_base<Ts>... {
    using component_types = std::tuple<Ts...>;

    // subclass implements this
    virtual void process_values(float delta_time, Ts&... ts) const = 0;
    
    void update(float delta_time) const {
        auto component_view = registry.get_view<Ts...>();
        for (component_types& c : component_view){
            process_values(delta_time, ?????);  // issue
        }
    }
};

The engine_system_base takes care of registering T for each type in Ts. On update each system implementation is supposed to retrieve all necessary components from the registry. I am unfortunately not sure how I can unpack the component_types instance to correctly call a subclass implementation.

Here is a full example (registry omitted):

// components are just "plain old data"
struct vec3 {
    float x, y, z;
};

struct transform_component {
    vec3 position, rotation, scale;
};

struct rigid_body_component {
    vec3 velocity, acceleration;
};

Components store state and have no behavior. Systems implement behavior based on Components.

// internal systems
template <typename T>
struct engine_system_base {
    engine_system_base() { /* register T for system in registry */ };
    virtual ~engine_system_base() = default;
};

template <typename... Ts>
struct engine_system : engine_system_base<Ts>...{
    using component_types = std::tuple<Ts...>;

    virtual void process_values(float delta_time, Ts&... ts) const = 0;
    
    void update(float delta_time) const {
        auto component_view = registry.get_view<Ts...>();
        for (auto& c : component_view){
            process_values(delta_time, ?????); // issue
        }
    }
};

engine_system_base registers T for a subclass implementation. engine_system uses engine_system_base as a variadic base to register each T in Ts with the registry (omitted). Afterwards a system can be implemented as such:

struct move_system : engine_system<transform_component, const rigid_body_component> {
    void process_values(float delta_time, transform_component& tc, const rigid_body_component& rb) const final {
        tc.position  = rb.velocity * delta_time;
    }
};

The move_system can then be used to translate all entities, which are comprised of a transform_component and rigid_body_component.

int main() {
    move_system ms{};
    ms.update(0.016); 
    return 0;
}

In my first implementation I defined void update(float delta_time) const individually for each system implementation, which works but duplicates the same exact implementation and only differs in by explicitly defining Ts... for each subclass. Unfortunately I am running into aforementioned issue when attempting to refactor this logic into engine_system.

CodePudding user response:

Replace ????? with std::get<Ts>(c)... assuming that each T in Ts is unique.

In case you have non-unique Ts, as mentioned in comments, you can use a std::index_sequence:

[&]<auto... Is>(std::index_sequence<Is...>) {
  process_values(delta_time, std::get<Is>(c)...);
}(std::index_sequence_for<Ts...>{});

CodePudding user response:

Assuming component_view type is component_types, std::apply might help:

void update(float delta_time) const {
    auto component_view = registry.get_view<Ts...>();
    for (auto& c : component_view){
        std::apply([&](auto&... args){ process_values(delta_time, args...); }, c);
    }
}
  • Related