I wish to make a function which accepts an arbitrary number of functor objects or more generally just callable objects (of different types) and applies them to an internal data structure. The function will be used with differing numbers of functors at different points in my code.
Rather than make different version to accept 1,2,3... etc and duplicate code I thought about using variadic templates.
I have found a solution which I will post below as an answer since I couldn't find anything on Google about specifically this and others might find it useful. But, please, if anyone has any better ideas post them. I have a feeling there should be a std way to do this?
I kind of knew this wouldn't work but my first attempt was
#include <iostream>
using namespace std;
struct FunctorA {
void operator() () {
cout << "FunctorA" << endl;
}
};
struct FunctorB {
void operator() () {
cout << "FunctorB" << endl;
}
};
template<typename... Fs>
void apply_functors(Fs... fs) {
fs()...; // will not work - can only expand packs in certain situations (e.g. as funciton
}
int main(void) {
apply_functors(FunctorA(),FunctorB());
apply_functors(FunctorA());
apply_functors([]()->void{ cout << "Lambda" << endl; });
return 0;
}
However this won't work because we are only allowed to expand parameter packs in certain situations and free like this is not one.
My next idea was to make a dummy function which did nothing but into which I could pass the functors as paramters then have them expanded. The reasoning behind this is that function paramters are one of the situations where we can expand paramter packs.
template<typename... Fs>
void _dummyF_impl(Fs... fs){}
template<typename... Fs>
void apply_functors(Fs... fs) {
_dummyF_impl( fs()... ); // only works if all functors return a value (else we get "invalid use of 'void'")
}
However, this will not work because the functors return void from their operator()
s and we cannot pass void as parameters to functions.
Expanding argument pack inside a braced initializer has the added benefit of guaranteed left-to-right evaluation (which is not the case for function argument list). You should also consider using perfect forwarding, here's why.
#include <initializer_list> //
#include <utility> // std::forward
template<typename... Fs>
void apply_functors(Fs&&... fs)
{
auto list = { (std::forward<Fs>(fs)(), 0)... };
// ^^^^ deduced as std::initializer_list
}
The same thing in principle, but this time without the necessary inclusion of <initializer_list>
is a variadic template constructor which you can also call with braced initializer:
struct dummy {
template<typename... Fs>
dummy(Fs&&... fs) { }
};
template<typename... Fs>
void apply_functors(Fs&&... fs)
{
dummy { (std::forward<Fs>(fs)(), 0)... }; // evaluated left-to-right, too
}
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