3

I have the following:

struct Args;

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

Which is great, 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);

All is nice and good, now I want to do two improvements here:

  1. Enforce that 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);
    
  2. Allow (in addition to the previous options) to pass an object with an specific named member function that would also be allowed to be a callback, example:

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

Is it possible?

Vraiment
  • 65
  • 5
  • Why not using `std::function` instead of a raw function pointer? – user0042 Jul 23 '17 at 08:50
  • 1
    @user0042 - What raw function pointer? This template accepts any callable type. – StoryTeller - Unslander Monica Jul 23 '17 at 08:56
  • Because I don't like the idea of constructing a new object every time I want to `DoStuff()`, a good question here is, how much overhead `std::function` creates? – Vraiment Jul 23 '17 at 08:58
  • 1
    @Vraiment - No, that is a question of premature optimization. The correct question is *"do I need the type erasure features of `std::function`, or is making my entire function a template good enough despite possible code bloat"*?. – StoryTeller - Unslander Monica Jul 23 '17 at 09:02
  • 1
    @StoryTeller good point, I guess you are right. That solves my first question, by using `std::function` I can enforce the callback signature – Vraiment Jul 23 '17 at 09:03
  • There is a very large overhead to std::function, both in creation and execution. Check for instance: https://www.codeproject.com/Articles/7150/Member-Function-Pointers-and-the-Fastest-Possible Your solution is generally even faster that, as it allows for inlining. If you really need the code complications in return for the extra speed, that is to be assessed in your specific use case. – Fabio Jul 23 '17 at 09:15

2 Answers2

2

Simple Approach

You can approach the problem just using std::function as argument or raw pointer.

The first version (std::function) is slightly better because allows to use 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 optimisations) is quite minimal, you can see in the demo assembly. Anyway if you need some advanced answers on those topic it's better to open a specific question.


About your point:

Enforce that 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 convertible to const Args&, so what you want to archive is not possible. Anyway bad cast are not allowed (with DoStuff_fn which I have presented).

void bad_callback(Args&) {}

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

Advanced Approach

Regards your question:

Allow (in addition to the previous options) to pass an object with an specific named member function that would also be allowed to be a callback

This is possible but with some advanced tricks.

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

If you need something more robust or C++11 compatible you should exploit SFINAE (here a good tutorial).

Instead my approach (to keep this question quite readable and easy):

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{});
  }
}

In that way the type T should be a functor with signature void(const Args&) convertible or, otherwise, it should have the perform_op method.


Here a demo.


Edit

If you want to exploit SFINAE your approach should be something like:

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{});
}
Community
  • 1
  • 1
BiagioF
  • 8,545
  • 2
  • 21
  • 45
  • This doesn't answer bullet 2 of the question. – skypjack Jul 23 '17 at 10:30
  • @skypjack why not? With SFINAE or boost example you can call `DoStuff_adv(myNamedCallback)`. I had even provided a example with C++17 – BiagioF Jul 23 '17 at 10:40
  • True. Anyway I suspect that an user that open such a question (or a future reader interested in this question) hardly do know how to implement `has_special_method`. You just solved a doubt by injecting around a new one. My two cents. – skypjack Jul 23 '17 at 12:02
0

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 you second question that can be solved using member function detection techniques. There exist many versions, for instance here or here.

André Bergner
  • 1,389
  • 9
  • 10