I'm trying to create an object that stores a tuple of objects created from a static
function but when the tuple
is created, the objects get copied into the tuple
instead of being placed directly into it, calling the destructor of each object in the tuple
twice, a behavior I would like to avoid. Is there a way to fix this without having to create custom move/copy constructors for each plugin class?
The code is as follows:
#include <tuple>
#include <iostream>
namespace details
{
template<typename PluginT, typename ContainerT, typename TupleT, size_t... Is>
static PluginT construct_plugin(ContainerT& container, TupleT&& tuple, std::index_sequence<Is...>)
{
return PluginT(container, std::get<Is>(std::forward<TupleT>(tuple))...);
}
template<typename PluginT, typename ContainerT, typename TupleT>
static PluginT construct_plugin(ContainerT& container, TupleT&& tuple)
{
return construct_plugin<PluginT>(container, std::forward<TupleT>(tuple), std::make_index_sequence<std::tuple_size<std::decay_t<TupleT>>::value>{});
}
}
struct simple_plugin
{
template<typename ContainerT>
simple_plugin(ContainerT& container) {}
~simple_plugin()
{
std::cout << "simple_plugin destructor" << std::endl;
}
};
struct plugin_with_params
{
template<typename ContainerT>
plugin_with_params(ContainerT& container, int argc, char* argv[]) {}
~plugin_with_params()
{
std::cout << "plugin_with_params destructor" << std::endl;
}
};
template<typename... PluginTs>
struct plugin_container
{
std::tuple<PluginTs...> plugins;
template<typename... TupleTs>
plugin_container(TupleTs&&... tuples) :
plugins(details::construct_plugin<PluginTs>(*this, std::forward<TupleTs>(tuples))...) {}
};
int main(int argc, char* argv[])
{
plugin_container<simple_plugin, plugin_with_params> container(std::make_tuple(), std::make_tuple(argc, argv));
return 0;
}
And here is the behaviour in action: https://godbolt.org/z/bqjv5r88x
struct nocopy{
nocopy(){}
nocopy(nocopy const&)=delete;
nocopy(nocopy&&)=delete;
};
struct maker{
operator nocopy()const{return {};}
};
std::tuple<nocopy,nocopy> t( maker{}, maker{} );
this bypasses the copy constructor in c++17 or above.
A similar technique can be used to get arbitrary functions that return an object to work.
The trick is that there isn't a constructor that accepts the maker
object, then C++ falls back on invoking the converting operator on the maker
, whose return value can be elided directly into the object.
Prior to 17, this may bypass the copy ctor at runtime but won't compile unless copy (or move) exists (even if the copy/move ctor has side effects). This is because 17 added guaranteed elision; before that it was optional.
With the static function it can't work, because elision through the constructor parameter of the std::tuple
is impossible.
Unfortunately std::tuple
lacks a std::piecewise_construct
constructor like std::pair
has, which could be used to have the action of your construct_plugin
be performed in-place in the constructor of the tuple/pair.
You can write your own tuple implementation supporting that, or you can add constructor overloads to your plugins that support construction from a tuple in the way that currently your static helper function does, or you can use the conversion function trick described in the other answer.
The reason that a copy is done rather than a move is only because you declared the destructors and copy constructors. Had you declared none then moves would have been used instead. Defining copy/move constructors can't help you avoid that they are called. The elision is impossible regardless of how the plugin classes are defined.
Also, you probably want std::forward_as_tuple
instead of std::make_tuple
, since the latter also makes a copy of the individual object before passing on to the plugin constructor, which I don't think is what you want (not that it would matter for the built-in types you are passing around).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With