Home > Blockchain >  Strange error while expanding parameter pack containing lambda types
Strange error while expanding parameter pack containing lambda types

Time:11-14

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)...);
}

Demo.

  • Related