Problem
I have a small “plugin system” (I’m not sure this is the right name). It allow you to store objects (plugins), and then call some methods from each of them. With this approach we have absolutely no overhead on iterating/object pass/callback call.
// Need this SET, because in C++11 auto can only be static const.
// And we may need to change them later.
#ifndef SET
#define SET(NAME, VALUE) decltype(VALUE) NAME = VALUE
#endif
Plugin1 plugin1(param1, param2);
Plugin2 plugin2(param1, param2);
Plugin3 plugin3(param1, param2);
SET(plugins, std::forward_as_tuple( // Pay attention here. We store &&, not objects
plugin1,
plugin2,
plugin3
));
Then, when I need to call some function from each of plugins, or do something with each of them, I iterate through plugins at compile time (I check this with generated asm code, and it just calls or even inline do_it
, without anything else) and call the callback function (I’m not showing iterate code here; it’s trivial):
struct Call{
float k=0;
template<typename T, int Index> // lambda function not efficient than this. Tested -O2 clang, gcc 4.8
inline void do_it(T &&t){
std::cout << "value = " <<t << " ; " << "id = " << Index << std::endl;
}
};
And if I need to do something with a specific plugin, I just directly use plugin1
, plugin2
, and plugin3
. No need to call std::get<>
. Plus the IDE highlights available plugins when you type them.
Is it ok to store rvalue references on objects, or I have to store objects directly in my tuple?
Like this:
SET(plugins, std::tuple(
Plugin1(param1, param2),
Plugin2(param1, param2),
Plugin3(param1, param2)
));
When I iterate, I pass a plugin as a usual reference:
struct Call{
float k=0;
template<typename T, int Index> // lambda function not efficient than this. Tested -O2 clang, gcc 4.8
inline void do_it(T &t){
std::cout << "value = " <<t << " ; " << "id = " << Index << std::endl;
}
};
With this approach we have additional move constructor calls for each element in the tuple. The previous version is free.
I ask about this because I previously read about data locality and now I worry about how data are placed in memory.
Solution
First, you are not storing &&
, but rather &
. Why? because plugin1
etc. are lvalue references, and because of the reference collapsing rules.
Second, you can use a tuple of references as you would use a reference itself. That is, everything is fine unless you return a tuple of references to local objects, in which case references are dangling.
Third, between forwarding (forward_as_tuple
) and copying (make_tuple
) there’s another option:
template<class... A>
constexpr std::tuple<A...> auto_tuple(A&&... a)
{
return std::tuple<A...>(std::forward<A>(a)...);
}
which keeps a reference to lvalues, and copies rvalues only. I find this most convenient.
In general, storing references or objects depends on what you want to do. Think of what you would choose if it was only one object, then keep the same choice for the tuple.
Check here for more.