Choose a type based on the lambda signature

advertisements

I am trying to determine a type based on the signature of a lambda expression.

I've come up with the following code, which works, but I'm wondering if there isn't a simpler way to go about it. I've posted a full working example on ideone.

template <typename T_Callback>
class CallbackType {
private:
    template <typename T>
    static CallbackFunctionA<T> testlambda(void (T::*op)(A const &) const);
    template <typename T>
    static CallbackFunctionB<T> testlambda(void (T::*op)(B const &) const);

    template <typename T>
    static decltype(testlambda<T>(&T::operator())) testany(int);
    template <typename T>
    static T &testany(...);
public:
    typedef decltype(testany<T_Callback>(0)) type;
};

In short, T_Callback can be either:

  • a lambda of the form [](A const &) { }
  • a lambda of the form [](B const &) { }
  • an instance of the class Callback or a class derived therefrom

It T_Callback is a lambda, a wrapper class derived from Callback should be returned (CallbackFunctionA or CallbackFunctionB), otherwise a reference to the callback type itself should be returned.

As I said the above code works just fine, but I'm wondering if it can be simplified, i.e. by removing the need for both testany and testlambda functions.


What you say you want:

  • a lambda of the form [](A const &) { } shall be mapped to CallbackFuncionA
  • a lambda of the form [](B const &) { } shall be mapped to CallbackFunctionB
  • an instance of the class Callback or a class derived therefrom shall be mapped to itself

What you actually have:

  • A functor having exactly one operator() returning nothing and having one argument of type const A& will be mapped to CallbackFunctionA
  • A functor having exactly one operator() returning nothing and having one argument of type const B& will be mapped to CallbackFunctionB
  • Anything else will be mapped to itself

My suggestion: Define a simple forwarder like this, for invoking when possible:

template<class X, class ARG...>
static auto may_invoke(const X& x, ARG&&... arg)
-> decltype(x(std::forward<ARG>(arg)...))
{ return x(std::forward<ARG>(arg)...); }
struct not_invoked {};
template<class X>
constexpr static not_invoked may_invoke(const X&, ...)
{ return {}; }

Also, a tester whether it can be invoked is nice to have:

template<class X, class ARG...>
constexpr bool does_invoke(const X& x, ARG&&... arg)
{ return !std::is_same<not_invoked,
      decltype(may_invoke(x, std::forward<ARG>(arg)...))>::value; }

That allows you to at the same time make your code more general and the tests more stringent.