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 tuple
s, but how do I forward each tuple
's type to the correct PluginTs
' constructor?
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;
}