9

I'm trying to implement a function template (in C++11) whose parameter is a lambda with arbitrary parameters, and return a compatible std::function object. The goal is for the returned function when called to invoke the original lambda asynchronously, but for now I'm just returning the original lambda.

The problem is simply getting the compiler to accept a lambda as the parameter of the function template. Here are some simple templates:

#include <functional>
using namespace std;

template <class Arg>
function<void(Arg)> pass1(function<void(Arg)> fn) {
    return fn;
}

template <class... Args>
function<void(Args...)> passn(function<void(Args...)> fn) {
    return fn;
}

They do the same thing, it's just that pass1 only works on single-parameter functors while passn takes an arbitrary number.

So now we try using them, first pass1:

    auto p1 = pass1( [](int a)->void {cout << a;} );  // ERROR

This doesn't work; the compiler can't seem to tell what parameters the lambda takes. Clang error message is:

Untitled.cpp:17:12: error: no matching function for call to 'pass1'
    auto p1 = pass1( [](int a)->void {cout << a;} );
              ^~~~~
Untitled.cpp:6:21: note: candidate template ignored: could not match 'function<void (type-parameter-0-0)>' against '(lambda at Untitled.cpp:17:19)'
function<void(Arg)> pass1(function<void(Arg)> fn) {

I can work around this by explicitly specifying the template parameter type:

    auto p2 = pass1<int>( [](int a)->void {cout << a;} );  // OK

However, this workaround fails with passn:

    auto p3 = passn<int>( [](int a)->void {cout << a;} );

Untitled.cpp:23:12: error: no matching function for call to 'passn'
    auto p3 = passn<int>( [](int a)->void {cout << a;} );
              ^~~~~~~~~~
Untitled.cpp:11:25: note: candidate template ignored: could not match 'function<void (int, type-parameter-0-0...)>' against '(lambda at Untitled.cpp:23:24)'
function<void(Args...)> passn(function<void(Args...)> fn) {
                    ^

The weird thing is that I can invoke passn if I pass it a function object:

    function<void(int)> fn = [](int a)->void {cout << a;};
    auto x = passn<int>(fn);  // OK

...in fact, I don't even have to specify the template parameter type:

    auto y = passn(fn);  // OK

The function I actually need is going to be like passn, but I don't want the extra verbiage of having to wrap a function object around the lambda every time I call it. Am I missing something, or is this just not possible? Would it be possible in C++14?

Jens Alfke
  • 1,946
  • 12
  • 15
  • you can simply wrap a `std::function` around the lambda at the call site, or as the dupe shows, change pass1/passn to accept general templates and restrict them with decltype if you must. – TemplateRex Feb 08 '17 at 23:13
  • @TemplateRex No problem, thanks for reopening. – nwp Feb 08 '17 at 23:25
  • Possible duplicate of [Type deduction does not work](http://stackoverflow.com/questions/40086513/type-deduction-does-not-work) – Guillaume Racicot Feb 08 '17 at 23:33

1 Answers1

6

You can use this implementation of passn:

#include <functional>
#include <iostream>

template <class RetVal, class T, class... Args>
std::function<RetVal(Args...)> get_fun_type(RetVal (T::*)(Args...) const);

template <class RetVal, class T, class... Args>
std::function<RetVal(Args...)> get_fun_type(RetVal (T::*)(Args...));

template <class T>
auto passn(T t) -> decltype(get_fun_type(&T::operator())) {
    return t;
}

int main() {
    auto fun = passn([](int a) { std::cout << a; });
    fun(42);
}

(demo)

It assumes you pass in a type that has an operator(). It takes the address of that function and deduces parameters from that member pointer.

The function will fail if you pass it an object that has multiple operator()s because then taking its address will be ambiguous, but lambdas will not produce that problem.

nwp
  • 9,623
  • 5
  • 38
  • 68
  • If you come here from the future and have access to C++17, deduction guides [4:28 video](https://www.youtube.com/watch?v=-3fVp0U4xi0) might be the better solution. – nwp Feb 08 '17 at 23:42