Search code examples
c++templatestuplesvariadic-templates

Creating a compile time plugin system with parameters in C++


I'm trying to create a compile-time plugin system for my game engine, but I'm having trouble dealing with passing parameters to each plugin.

Here's some code to show what I mean:

#include <tuple>

namespace engine
{
    struct base_plugin
    {
        template<typename EngineT>
        base_plugin(EngineT& engine) {}
        
        ~base_plugin() = default;
    };
    
    template<typename... PluginTs>
    struct engine
    {
        std::tuple<PluginTs...> plugins;
        
        engine() :
            plugins((sizeof(PluginTs), *this)...) {}
    };
}

struct other_plugin
{
    template<typename EngineT>
    other_plugin(EngineT& engine) {}

    ~other_plugin() = default;
};

int main(int argc, char* argv[])
{
    engine::engine<engine::base_plugin, other_plugin> engine;

    return 0;
}

This works fine, but now I want to be able to accept plugins that have more than the EngineT& as parameters. I was thinking of doing this with tuples, but how do I forward each tuple's type to the correct PluginTs' constructor?


Solution

  • I've managed to find my answer thanks to the hints from Constructor arguments from a tuple. What I needed was a way to construct a plugin with a custom parameter (EngineT&) and then unpack the respective tuple for the next parameters. Unpacking the tuple implies building an std::index_sequence with the size of the tuple, using std::get<Index> to get the type with that index and finally using ellipsis... to do it for all of the tuple's types. This was done with two separate construct_plugin functions, one to generate the sequence and one to unpack the tuple using it. In C++17 there is a method that works in a similar way (std::make_from_tuple) but since my use case required to inject an extra EngineT& parameter at the beginning, it couldn't be used.

    I also added a member to engine and some parameters to base_plugin and added some debug prints to verify that everything properly.

    Final code:

    #include <tuple>
    #include <iostream>
    
    namespace engine
    {
        namespace details
        {
                template<typename PluginT, typename EngineT, typename TupleT, size_t... Is>
                PluginT construct_plugin(EngineT& engine, TupleT&& tuple, std::index_sequence<Is...>) 
                {
                    return PluginT(engine, std::get<Is>(std::forward<TupleT>(tuple))...);
                }
    
                template<typename PluginT, typename EngineT, typename TupleT>
                PluginT construct_plugin(EngineT& engine, TupleT&& tuple) 
                {
                    return construct_plugin<PluginT>(engine, std::forward<TupleT>(tuple), std::make_index_sequence<std::tuple_size<std::decay_t<TupleT>>::value>{});
                }
        }
    
        struct base_plugin
        {
            template<typename EngineT>
            base_plugin(EngineT& engine, int argc, char* argv[]) 
            {
                std::cout << "Base plugin reports test is " << engine.test << std::endl;
                std::cout << "Base plugin reports argc is " << argc << std::endl;
                std::cout << "Base plugin reports argv is " << argv << std::endl;
            }
    
            ~base_plugin() = default;
        };
        
        template<typename... PluginTs>
        struct engine
        {
            int test;
            std::tuple<PluginTs...> plugins;
    
            template<typename... TupleTs>
            engine(TupleTs&&... tuples) :
                test(42),
                plugins(details::construct_plugin<PluginTs>(*this, std::forward<TupleTs>(tuples))...) {}
        };
    }
    
    struct other_plugin
    {
        template<typename EngineT>
        other_plugin(EngineT& engine) 
        {
            std::cout << "Other plugin reports test is " << engine.test << std::endl;
        }
    
        ~other_plugin() = default;
    };
    
    int main(int argc, char* argv[])
    {
        engine::engine<engine::base_plugin, other_plugin> engine(std::make_tuple(argc, argv), std::make_tuple());
        return 0;
    }