6
votes

I'm trying to pass lambda to std::function<> defined via variadic template, but it seems that this doesn't work on gcc.

Is there any reason, why this code doesn't work on gcc 7.4.0 but working correctly on Visual Studio 2017? And is there any way how to make it work also on gcc without the necessity to manually convert it to std::function<> first?

#include <functional>

template<class ...TParams>
int TestFunction(std::function<void(TParams...)> )
{
    return 0;
}

void Test()
{
    auto fce = [](int /*n*/, double /*d*/) {};

    //This doesn't work with error no matching function for call to 'TestFunction<int, double>(Test()::<lambda(int, double)>&)'
    TestFunction<int, double>(fce);

    //but this works correctly
    std::function<void(int, double)> fce2 = fce;
    TestFunction<int, double>(fce2);
}

I'm getting following error:

main.cpp: In function 'void Test()':
main.cpp:116:31: error: no matching function for call to 'TestFunction<int, double>(Test()::<lambda(int, double)>&)'
  TestFunction<int, double>(fce);
                               ^
main.cpp:106:5: note: candidate: template<class ... TParams> int TestFunction(std::function<void(TParams ...)>)
 int TestFunction(std::function<void(TParams...)> fceCallback)
     ^~~~~~~~~~~~
main.cpp:106:5: note:   template argument deduction/substitution failed:
main.cpp:116:31: note:   'Test()::<lambda(int, double)>' is not derived from 'std::function<void(TParams ...)>'
  TestFunction<int, double>(fce);
                               ^
1

1 Answers

5
votes

A trailing template parameter pack always leaves room for further deduction. Specifying the first two arguments doesn't prevent you from doing something like this:

std::function<void(int, double, char)> fce3 ;
TestFunction<int, double>(fce3);

In this case, the pack will contain int, double, char, because the char was deduced from the function argument. Now, because deduction isn't over, and a lambda is not a std::function, the substitution fails.

To make this work, you need to let the deduction process know it's over, that an instantiated function is needed now, before it's given an argument. One way to do that is to take the function's address, for instance:

auto pfunc = TestFunction<int, double>;
pfunc(fce);

or

(&TestFunction<int, double>)(fce);

Taking a function template's address is another context where template argument deduction can occur. In this case, the trailing pack is deduced as empty, and you get a pointer to a function you may call.