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...