简体   繁体   中英

Variadic template function accepting lambda

I'm trying to understand the compiler error that I'm getting fo the code below. I've got a variadic template function which accepts a lambda with the specified types, and attempting to call that function results in the template not being considered a valid candidate due to a mismatch.

#include <functional>

template<typename ... ResultTypes>
void executeWithResultHandler(std::function<void (ResultTypes...)> lambda)
{
}

int main(int argc, char **argv)
{
    executeWithResultHandler<int>([] (int arg) {
    });
    return 0;
}

This results in the following error:

$ c++ -std=c++11 reduction.cpp 
reduction.cpp:10:5: error: no matching function for call to 'executeWithResultHandler'
    executeWithResultHandler<int>([] (int arg) {
    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
reduction.cpp:4:6: note: candidate template ignored: could not match 'function<void (int, type-parameter-0-0...)>' against
      '<lambda at reduction.cpp:10:35>'
void executeWithResultHandler(std::function<void (ResultTypes...)> lambda)
     ^
1 error generated.

If I change the declaration to not be variadic:

template<typename ResultType>
void executeWithResultHandler(std::function<void (ResultType)> lambda)
{
}

then it works for the toy example above, but for the real problem I need arbitrary arguments. Is there something I'm missing here, or anther way to accomplish this?

EDIT: This was marked as a duplicate incorrectly, I believe- the dupe does not answer the question I'm asking. This question specifically has to do with the variadic template issue here: Please note that, when I switch the template to be non-variadic the lambda converts to the std::function type correctly, as expected. This is true regardless of the number of arguments, as long as that is not handled in a variadic fashion.

However, it does not work with the variadic version specifically, despite an expectation that the parameter pack is unpacked to a set of real parameters, and the explicit specification of the template parameter list at the function call site.

The problem with variadic templates in your case is that the compiler does not know whether the int you explicitly specified is the complete list for ResultTypes... or not, so it tries to deduce the optional remaining arguments from the parameter you gave it, and that obviously fails. This is a common pitfall with variadic template arguments and it is not limited to lambdas.

A solution always implies that you take away this option from the compiler, eg

template<typename ... ResultTypes>
void executeWithResultHandler_impl(std::function<void (ResultTypes...)> lambda)
{
}

template<typename ... ResultTypes, typename F>
void executeWithResultHandler(F&& lambda)
{
    executeWithResultHandler_impl(std::function<void (ResultTypes...)>(lambda));
}

The question I'd previously linked as duplicate explains exactly what is going in your case.

An std::function is not a lambda, it is type of container that can store any kind of callable object. You can assign a lambda to an std::function , but in that case the necessary conversion is performed by the std::function constructor.

In your example

template<typename ... ResultTypes>
void executeWithResultHandler(std::function<void (ResultTypes...)> lambda)
{}

executeWithResultHandler<int>([](int arg){});

the compiler has no way of inferring the types in the parameter pack ResultTypes from the lambda expression above. Template argument deduction requires exact matches, implicit conversions are not considered, and the types involved here, as mentioned earlier, are completely different.


If I change the declaration to not be variadic then it works

template<typename ResultType>
void executeWithResultHandler(std::function<void (ResultType)> lambda)
{}

 executeWithResultHandler<int>([](int arg){});

This works because there's no template argument deduction involved anymore. executeWithResultHandler takes exactly one template parameter, which you've explicitly specified, and because a lambda is implicitly convertible to std::function , overload resolution will find a match.

Note that in the first case there may have been more template arguments, in addition to int , that you hadn't specified explicitly.


You can get your original example to work by explicitly converting the lambda to std::function .

executeWithResultHandler<int>(std::function<void(int)>([] (int arg) {}));

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM