1

I'm creating a generic C++ EventEmitter. It's based on Node.js EventEmitter:

template <typename ...Args>
int16_t EventEmitter::addListener(uint32_t eventId, std::function<void(Args...)> cb)
{
    ...
}

template <typename... Args>
void EventEmitter::emit(uint32_t eventId, Args... args)
{
    ...
}

It's working as expected (I can register listeners with different prototypes). Eg.:

auto handler = [](int n) { ... };

listener.addListener(0, std::function<void(int)>(handler));

But I don't want to bother typing the whole listener prototype to std::function<...> every time I add one (some have more than 5 parameters), then I decided to create a macro:

#define STDFUNC(fn) std::function<decltype(fn)>(fn)

The problem is when I try to use it with lambdas: decltype(handler) is not void(int), it's class lambda []void (int n)->void instead, generating the error:

(Clang 3.7.1) -> error : implicit instantiation of undefined template 'std::_Get_function_impl<(lambda at ...

I'm scratching my head to get the prototype without that lambda qualifiers but I'm stuck. Any help will be appreciated.

karliwson
  • 3,365
  • 1
  • 24
  • 46
  • 1
    Why are you adding listeners with *different* signatures in the first place? – n. m. could be an AI Nov 25 '17 at 06:38
  • @n.m. every event has its own parameters, the emit method is: `template void emit(uint32_t eventId, Args... args)` – karliwson Nov 25 '17 at 06:42
  • 1
    What an interesting problem. You know, lambda types have to declare an `operator ()`. What you want is the signature of that. Also, using a pre-processor macro is evil, and you should avoid it if at all possible. In the case of STDFUNC there, you could've easily defined a template function that did that, and you should've. – Omnifarious Nov 25 '17 at 06:56
  • @Omnifarious exactly, but the lambda object is "abstracted" by the language, as I understand. About the macro, I will define a template as you suggested, as soon as I figure it out. Thanks. – karliwson Nov 25 '17 at 06:59
  • 1
    Is your problem different to this one? https://stackoverflow.com/questions/11893141/inferring-the-call-signature-of-a-lambda-or-arbitrary-callable-for-make-functio – John Zwinck Nov 25 '17 at 07:05
  • @JohnZwinck it seems to be an alternative, I'm testing it. – karliwson Nov 25 '17 at 07:10
  • @JohnZwinck it worked. I always wonder how horrendous template code does beautiful things =) – karliwson Nov 25 '17 at 07:19

1 Answers1

2

Here is a small program that generates a function object for an arbitrary lambda:

#include <functional>
#include <type_traits>

template <typename R, typename... A>
class build_func_type
{
 public:
   using type = ::std::function<R(A...)>;
};

template <typename R, typename C, typename... A>
typename build_func_type<R, A...>::type mem_func_to_func( R(C::*)(A...) const)
{
   return nullptr;
}

template <typename T>
decltype(mem_func_to_func(&T::operator ())) lambda_to_fp(T le)
{
    using func_t = decltype(mem_func_to_func(&T::operator ()));
    return func_t{le};
}

int test()
{
   auto foo = [](int x) -> int { return x * x; };
   auto ftype = lambda_to_fp(foo);
   return ftype(5);
}

It uses the function mem_func_to_func to auto-deduce the type of the lambda's operator (). It then uses the build_func_type template to build a function type out of the components of the type of the lambda's operator (). I could possibly have used a constructor in build_func_type and relied on C++17 constructor type deduction for this.

Then lambda_to_fp will take a lambda, using mem_func_to_func to create a pointer to the appropriate function type from a pointer to the lambda's operator () member function. Then it creates a ::std::function of the appropriate type, constructing that type from the type of the function pointer. Then it initializes it with the lambda and returns it.

Omnifarious
  • 54,333
  • 19
  • 131
  • 194
  • Is it possible without the `auto` return type in `lambda_to_fp`? Seems like Clang 3.7.1 (at least with the default settings) can't compile it because it's a C++14 extension. – karliwson Nov 25 '17 at 07:39
  • @karliwson - I'm not sure. It would be tricky to do. You could give clang -std=c++14. :-) I'll think awhile on how that might be fixed. Do you understand basically what's going on with it though? The code in general I mean. – Omnifarious Nov 25 '17 at 07:41
  • Kind of. You're getting the lambda's `operator()` and using some magic to extract its prototype and create a `std::function` with it. Btw, the `-std=c++14` is not available in Clang 3.7.1 (I'm using LLVM for Windows). – karliwson Nov 25 '17 at 07:46
  • @karliwson - There, I fixed it. – Omnifarious Nov 25 '17 at 07:56
  • The type of the address of the lambda function's `operator ()` is going to be `R (C::*)(A...) const` where R is the return type, C is the type of the lambda itself, and A... is a list of all the argument types. The trick is to convert that into a `::std::function` type. It has to be done with a function because the compiler will only deduce the template arguments for a function. In C++17 the compiler will deduce the template arguments for a constructor, but you didn't even want C++14. :-) – Omnifarious Nov 25 '17 at 08:00
  • Great, it worked! Comparing to the answer John Zwinck mentioned, your solution is way more elegant. Thank you very much! – karliwson Nov 25 '17 at 08:00