1

If I have a templated function that takes a lambda expression how do I specialize it so that it can work with lambda expressions that take differing numbers of arguments ? The following code works:

template<typename Lambda>
void doSomething(Lambda callback)
{
    callback();
}

doSomething([] () { /* some code here */ } );

but if I want to also be able to call it like this:

doSomething([&foo] (int x)        { /* some code here */ } );
doSomething([&foo] (int x, int y) { /* some code here */ } );

what syntax do I use to provide the specialized versions of doSomething() ? This syntax:

template<>
void doSomething<[] (int)>([] (int) callback)
{
    callback(3);
}

doesn't compile under Visual Studio 2012 and I haven't been able to find any references to what a valid syntax would look like. Is it possible ? Alternatively, is it possible to tell from inside doSomething() how many arguments the supplied callback takes so that I can do something like this:

template<typename Lambda>
void doSomething(Lambda callback)
{
    int numArgs = ???
    switch (numArgs) {
        case 0: callback();    break;
        case 1: callback(1);   break;
        case 2: callback(1,2); break;
    }
}
Glenn Coombs
  • 153
  • 2
  • 11

2 Answers2

0

There are 2 cases for lambdas: when they're capturing and when they're not.

For the first case, you need to extract the return type and parameter types of the lambda (from the operator() of the lambda), it is covered in this question.

For the last case, you can use function pointer specialization, because non-capturing lambdas have a function pointer conversion operator.

Regarding your doSomething() function, it is impossible to directly call the functor according to the deduced number of arguments, as you've done here:

template<typename Lambda>
void doSomething(Lambda callback)
{
    int numArgs = ???
    switch (numArgs)
    {
        case 0: callback();    break;
        case 1: callback(1);   break;
        case 2: callback(1,2); break;
    }
}

(The needed feature is static if and has been proposed for the next C++ standard, see n3322 and n3329)

You need to use a helper function, or structure, which is specialized with a specific function signature. Something like:

template<typename T> struct helper {};
template<typename R, typename P1>
struct helper<R(P1)>
{
    static R call(R (*lambda)(P1)) { return lambda(magic_param); }
};
template<typename R, typename P1, typename P2>
struct helper<R(P1,P2)>
{
    static R call(R(*lambda)(P1,P2)) { return lambda(magic_param_1,magic_param_2); }
};
// ...
template<typename R, typename... A>
void doSomething(R (*callback)(A...))
{
    helper<R(A...)>::call(callback);
}

Which brings an important question: how would you generate parameters (magic_param...) ?

Community
  • 1
  • 1
Synxis
  • 9,236
  • 2
  • 42
  • 64
  • I thought I'd scoured stackoverflow for similar questions but I hadn't seen the one from ecatmur that you pointed out. Many thanks for that as I believe the answer does indeed provide a way to do what I want. – Glenn Coombs Nov 18 '12 at 21:56
0

Using the link to ecatmur's answer provided by Synxis I have managed to code something which does what I wanted. I rewrote the templates to use typedef rather than the new template alias use of the using keyword so the code will work on older compilers. In case anyone else wants to do something similar I have included the code below to show how it can be done:

template<typename T> struct remove_class { };

template<typename C, typename R, typename... A>
struct remove_class<R(C::*)(A...)> { typedef R(*type)(A...); };
template<typename C, typename R, typename... A>
struct remove_class<R(C::*)(A...) const> { typedef R(*type)(A...); };
template<typename C, typename R, typename... A>
struct remove_class<R(C::*)(A...) volatile> { typedef R(*type)(A...); };
template<typename C, typename R, typename... A>
struct remove_class<R(C::*)(A...) const volatile> { typedef R(*type)(A...); };

template<typename T>
struct get_function_signature {
    typedef typename remove_class<decltype(&std::remove_reference<T>::type::operator())>::type type;
};

template<typename R, typename... A>
struct get_function_signature<R(A...)> { typedef R(*type)(A...); };
template<typename R, typename... A>
struct get_function_signature<R(&)(A...)> { typedef R(*type)(A...); };
template<typename R, typename... A>
struct get_function_signature<R(*)(A...)> { typedef R(*type)(A...); };

// ***************************************************************************

template<typename T> struct num_args {};

template<typename R, typename... A>
struct num_args<R(*)(A...)> { static const int value = sizeof...(A); };

template<typename C, typename R, typename... A>
struct num_args<R(C::*)(A...)> { static const int value = sizeof...(A); };

// ***************************************************************************

template<typename Lambda, int> struct callWithArgsImpl {};

template<typename Lambda>
struct callWithArgsImpl<Lambda, 1> {
    static void doIt(Lambda callback, Object* pObj, int arg) { callback(pObj); }
};

template<typename Lambda>
struct callWithArgsImpl<Lambda, 2> {
    static void doIt(Lambda callback, Object* pObj, int arg) { callback(pObj, arg); }
};

template<typename Lambda, int N>
void callWithArgs(Lambda callback, Object* pObj, int arg)
{
    callWithArgsImpl<Lambda, N>::doIt(callback, pObj, arg);
}

// ***************************************************************************

template<typename Lambda>
void doSomething(int x, Lambda callback)
{
    // some code here which gets a pointer to an Object (pObj) and an
    // extra piece of information about where the object came from (arg1)

    const int numArgs = num_args<typename get_function_signature<Lambda>::type>::value;

    callWithArgs<Lambda, numArgs>(callback, pObj, arg1);
}

That allows code to call the doSomething() function passing in a lamba expression which always expects an Object* parameter but the 2nd argument can be omitted if not required. So both of the following lines work:

doSomething(5, [] (Object* pObj)           { printf("lambda called with no arg1\n"); } );
doSomething(2, [] (Object* pObj, int arg1) { printf("lambda called with arg1=%d\n", arg); } );

It works but I can't help feeling that it works in spite of the language specification rather than because of it. Using lambda expressions as callback functions seems like a natural thing to do. And I can definitely see cases where I would want to specify callbacks with different signatures. This should be easier to do than this...

Glenn Coombs
  • 153
  • 2
  • 11