I have a function which looks like foo
in the following example:
template <typename... Parameters>
void foo(std::function<void (Parameters &)>... functions) {
// does interesting things with these functions
}
Now I want to call this function with some lambdas, e.g. like this:
foo([](const std::string & string) {});
Unfortunately that doesn't work, because I get the following error:
error: no matching function for call to 'foo'
note: candidate template ignored: could not match 'function<void (type-parameter-0-0 &)>' against '(lambda at file.cpp:50:23)'
AFAIK, that is, because lambdas cannot be implicitly converted to std::functions
like that.
One way to solve this problem is to manually wrap the lambdas in std::function
like so:
foo(std::function<void (const std::string &)>([](const auto & string) {}));
But for multiple lambdas this would get very tedious.
To get around this problem, I tried to create a wrapper function which detects the parameter type of the lambdas it gets passed using a helper type, and then wraps the lambda in the correct std::function
type. Here is this wrapper function for only a single parameter (i.e. not variadic):
template <typename Function>
void fooWrapped(Function && function) {
foo(std::function<void (typename FunctionTypeTraits<Function>::ParameterType &)>(function));
}
The helper type FunctionTypeTraits
is implemented like this:
template <typename Function>
class FunctionTypeTraits:
public FunctionTypeTraits<decltype(&std::remove_reference<Function>::type::operator())> {};
template <typename Param>
class FunctionTypeTraits<void (&)(Param &)> {
typedef Param ParameterType;
};
Now I can call the wrapper function with my lambda and the compiler is perfectly happy:
fooWrapped([](const std::string & string) {});
In principle, I should now be able to make fooWrapper
variadic like so:
template <typename... Functions>
void fooWrapped(Functions &&... functions) {
foo((std::function<void (typename FunctionTypeTraits<Functions>::ParameterType &)>(functions))...);
}
That doesn't work however. If I call this new function with the exact same code, I get the following error:
error: 'std::remove_reference<void ((lambda at file.cpp:50:23)::*)(const std::string &) const>::type' (aka 'void ((lambda at file.cpp:50:23)::*)(const std::string &) const') is not a class, namespace, or enumeration
I don't quite understand this error. Why does the same approach work for a single template type, but not for an expanded parameter pack? Is this maybe just a compiler bug?
Is there another way, I could achieve my goal of calling foo
using lambdas, without manually wrapping each of them in a std::function
?
CodePudding user response:
The type of address of lambda's operator()
is void (Lambda::*)(Param&) const
not void (&)(Param &)
, you need to define the base case of your FunctionTypeTraits
as:
template <typename Function>
struct FunctionTypeTraits:
public FunctionTypeTraits<decltype(&std::remove_reference<Function>::type::operator())> {};
template <typename Lambda, typename Param>
struct FunctionTypeTraits<void (Lambda::*)(Param) const> {
typedef Param ParameterType;
};
Another point is that in your fooWrapped
, the type of specify for std::function
should be void (typename FunctionTypeTraits<Function>::ParameterType)
instead of just ParameterType
since the latter is not a function type:
template <typename... Function>
void fooWrapped(Function&&... function) {
foo(std::function<void (typename FunctionTypeTraits<Function>::ParameterType)>(function)...);
}