C ++ overload pattern with specific signature

I have the following:

struct Args;

template <typename T>
void DoStuff(T&& callback) {
    // ... do stuff
    MyArgs args = ...
    callback(args);
}

      

What's great is I can do the following:

void callback(const Args &);
DoStuff(callback);

      

and

DoStuff([](const Args &) { ... });

      

and

class CallbackClass {
    operator()(const Args &);
};
CallbackClass myCallbackClass;
DoStuff(myCallbackClass);

      

Everything is good and good, now I want to make two improvements here:

  • Make sure the signature is T

    always void(const Args &)

    , because in the current state of things I can do the following:

    void badCallback(Args);
    DoStuff(badCallback);
    
          

  • Allow (in addition to the previous parameters) to pass an object with a specific named member function that can also be a callback, for example:

    class NamedCallback {
        void PerformCallback(const Args &);
    };
    NamedCallback myNamedCallback;
    DoStuff(myNamedCallback);
    
          

Is it possible?

+3


source to share


2 answers


A simple approach

You can approach the problem simply by using std::function

a raw pointer as an argument.

The first version ( std::function

) is slightly better because it allows you to use an object functor.

void DoStuff_fn(std::function<void(const Args&)> callback) { callback(Args{}); }

void DoStuff_raw(void (*callback)(const Args&)) { callback(Args{}); }

struct Functor {
  void operator()(const Args&) {}
};

// ...
DoStuff_fn(Functor{});      // right call operator() of object
// DoStuff_raw(Functor{});  // error no conversion available

      

The overhead (with optimization) is pretty minimal, you can see in the demo build. Anyway, if you want some advanced answers on this topic, it's best to open a specific question.


About your point:

Ensure the signature of T is always void (const Args &), because in the current state of things I can do the following:

void badCallback(Args);
DoStuff(badCallback);

      

Args

is implicitly converted to const Args&

, so what you want to archive is not possible. Anyway, a bad actor is not allowed (with DoStuff_fn

which I presented).

void bad_callback(Args&) {}

// ...
// DoStuff_fn(bad_callback);  // error! cannot cast Args& --> const Args&

      


Advanced approach

Best regards, your question:

Allow (in addition to the previous parameters) to pass an object with a specific named member function, which will also be resolved as a callback



It is possible, but with some advanced tricks.

What I can suggest you in this question is a very simple approach (using C ++ 17 constexpr if

).

If you want something more robust or C ++ 11 compliant you should use SFINAE

( good tutorial here ).

Instead, my approach (to keep this question readable and easy enough) is:

using Signature = std::function<void(const Args&)>;
template <typename T>
void DoStuff_adv(T&& functor) {
  if constexpr (std::is_convertible<T, Signature>::value) {
    functor(Args{});
  } else {
    functor.perform_op(Args{});
  }
}

      

So the type T

must be a functor with a signature void(const Args&

), or it must have a method otherwise perform_op

.


Here's a demo.


Edit

If you want to use SFINAE, your approach should look something like this:

using Signature = std::function<void(const Args&)>;

template <typename T>
typename std::enable_if<std::is_convertible<T, Signature>::value>::type
DoStuff_adv(T&& functor) {
  functor(Args{});
}

template <typename T>
typename std::enable_if<has_special_method<T>::value>::type
DoStuff_adv(T&& functor) {
  functor.perfom_op(Args{});
}

      

Boost Solution With Boost Library:

#include <boost/tti/has_member_function.hpp>

using Signature = std::function<void(const Args&)>;

template <typename T>
typename std::enable_if<std::is_convertible<T, Signature>::value>::type
DoStuff_adv(T&& functor) {
  functor(Args{});
}

BOOST_TTI_HAS_MEMBER_FUNCTION(perform_op)

template <typename T>
typename std::enable_if<
    has_member_function_perform_op<void (T::*)(const Args&)>::value>::type
DoStuff_adv(T&& functor) {
  functor.perform_op(Args{});
}

      

+2


source


Your first question can be solved like this:

#include <type_traits>
#include <tuple>

// the generic signature falls back on the signature of the call operator if present
template <class C>
struct signature : signature< decltype( &std::decay_t<C>::operator() ) >  {};

// pointer to member function fall back on the plain function signatures
template < class C , typename Result , typename... Args >
struct signature< Result (C::*)(Args...) > : signature< Result ( Args... ) > {};

template < class C , typename Result , typename... Args >
struct signature< Result (C::*)(Args...) const > : signature< Result ( Args... ) > {};

// pointer and references to free function fall back on the plain function signatures
template < typename Result , typename... Args >
struct signature< Result (*)(Args...) > : signature< Result ( Args... ) > {};

template < typename Result , typename... Args >
struct signature< Result (&)(Args...) > : signature< Result ( Args... ) > {};

// actual implementation just for pure function signature types
template < typename Result , typename... Args >
struct signature< Result ( Args... ) >
{
   static constexpr auto num_args = sizeof...(Args);

   template< size_t n >
   using  argument = typename std::tuple_element< n, std::tuple<Args...> >;
   using  result_type = Result;
};

template <typename Callable, size_t N >
using argument_t = typename signature<Callable>::template argument<N>::type;


// -------------------------------------------

struct Args {};

template <typename T> // could use enable_if as well
                      // , typename = std::enable_if_t<std::is_same_v<argument_t<T,0>,const Args&>>>
void DoStuff(T&& callback) {
    static_assert(std::is_same_v<argument_t<T,0>,const Args&>, "Callback has the wrong signature");
    // ... do stuff
    Args args = {};
    callback(args);
}

void callback(const Args &) {}

struct CallbackClass {
    void operator()(const Args &){}
};


int main()
{
    DoStuff(callback);
    DoStuff(CallbackClass());
    DoStuff([](const Args &) {  });
    // DoStuff([](Args) {  });  // won't compile, triggers static assertion informing the user about the error.
}

      



DEMO

Regarding the second question that can be solved with member function detection methods. There are many versions, for example here or here .

0


source







All Articles