Search code examples
c++variadic-templatesvariadic-functionsvariadic

Unpacking a tuple to call a function templated with variadic arguments in a subclass implementations (C++)


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). Afterward, 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 the aforementioned issue when attempting to refactor this logic into engine_system.


Solution

  • 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);
        }
    }