0

I need to store callback functions from opencl to later execution, e.g:

void pfn_notify(const char *errinfo, const void *private_info, size_t cb, void *user_data){
    fprintf(stderr, "OpenCL Error (via pfn_notify): %s\n", errinfo);
}

These callback functions vary, so I would like to unify storing method and store pointers to them in some kind of container(vector, map etc.). In addition some of the arguments needs to be passed after storing instead of binding in the moment of pushing into container.

  1. General pseudocode scheme:

    ...some code in main...
    Client.storecallback(function_to_store(some_arguments));
    ...rest code in main...
    
    class Client{
      void storecallback(void*(*)(some_arguments) function){
        callback_thread.store(function);
        callback_thread.start();
      }
    
      CallbackThread callback_thread;
    };
    
    class CallbackThread() {
      void start(){
       /* make connection via tcp/ip */
       receive();
      }
      void store(void*(*) function){
        callback.store(key, function);
      }
    
      void receive() {
        Buffer* input = new Buffer(tcp_socket);
        int a = input->pull();
        char b = input->pull();
        callback.raise(key, a, b);
      }
    
      Callback callback;
    };
    
    class Callback {
      std::map<uint64_t key, void*(*) function> callback_map;
      void store(void*(*) function){
        callback_map[key] = function;
      }
      template<typename... Args>
      void raise(uint64_t key, Args... rest_of_arguments){
        callback_map[key](rest_of_arguments);
      }
    };
    

I am aware that i need extra class for unification, some kind of Functor class.

  1. With use of std::function and std::bind I am able to unificate stored variable type to std::function<void()> however, I can not change/bind new arguments to callback functions. This solution requries universal struct which will store pointers to arguments variables and replace/fill them with data before calling stored function and I have no idea how to create such an universal structure other than structure templates for each of callback function which is not really nice solution for me.

  2. With assist of this Indicies, and this Stackoverflow I was able to create solution in which I can unificate creation process with placeholders which allows me to add some arguments in moment of calling, not only storing:

     namespace project_name {
       namespace detail {
        template<int I> struct placeholder {};
       }
     }
     namespace std {
       template<int I>
       struct is_placeholder< project_name::detail::placeholder<I> > :
       std::integral_constant<int, I>{};
     }
     namespace project_name {
       namespace detail {
         template <size_t... Is>
         struct indices {};
    
         template <size_t N, std::size_t... Is>
         struct build_indices : build_indices<N - 1, N - 1, Is...> {};
    
         template <size_t... Is>
         struct build_indices<0, Is...> : indices<Is...> {};
    
         template<std::size_t... Is, class F, class... Args>
         inline auto easy_bind(indices<Is...>, F const& f, Args&&... args) ->
         decltype(std::bind(f, std::forward<Args>(args)..., placeholder<Is + 1> {}...)){
        return std::bind(
          f, std::forward<Args>(args)..., placeholder<Is + 1> {} ...);
       }
     }
    
     class Functor {
       public:
    
       template<class R, class... FArgs, class... Args>
       Functor(std::function<R(FArgs...)> f, Args&&... args) {}
    
       template<class R, class... FArgs, class... Args>
       static inline auto easy_bind(std::function<R(FArgs...)> f, Args&&... args) -> decltype(detail::easy_bind(
      detail::build_indices<sizeof...(FArgs) - sizeof...(Args)> {},
      f, std::forward<Args>(args)...)) {
        return detail::easy_bind(
        detail::build_indices<sizeof...(FArgs) - sizeof...(Args)> {}, f, std::forward<Args>(args)...);
     }
    }
    

Unfortunately now I have no clue how to unify storing this, as returned types are different when using std::placeholder.

Here are my questions then:

  1. Which of these two approaches is better and how to solve problems of this approach.
  2. Maybe there is other approach which I should consider but did not think of due to lack of knowledge.

EDIT: Your comments with links to threads about problems not even close to mine are absolutely not useful. Read what question is about before linking something that I have already implemented and inserted into my question. Solved it anyway, I'll add solution for further generations soon.

PStarczewski
  • 479
  • 2
  • 17
  • 1
    Sounds like you want unique `std::function` signatures an capturing lambda functions. – πάντα ῥεῖ Dec 08 '18 at 19:14
  • 4
    Ask two C++ developers which two approaches are better, and you will get three answers and four alternatives. – Sam Varshavchik Dec 08 '18 at 19:14
  • For everyone downvoting please let me know what is not right with this question in your opinion as I have no clue. – PStarczewski Dec 08 '18 at 19:16
  • And one alternative might be classes which inherits an abstract base-class with an `virtual void operator()()` function. That function-call operator can be implemented in the child classes to calls whatever other function it need with whatever arguments needed. And the arguments could be members in the child-class initialized through the constructor. – Some programmer dude Dec 08 '18 at 19:17
  • @πάνταῥεῖ Could you please expand your comment? – PStarczewski Dec 08 '18 at 19:17
  • @SamVarshavchik True, but sometines some of the solutions have major disadvantages. – PStarczewski Dec 08 '18 at 19:17
  • @PStarczewski What's particularly unclear about it (that you can't find at google yourself)? – πάντα ῥεῖ Dec 08 '18 at 19:18
  • @πάνταῥεῖ How unique signatures of `std::function` helps my case. I still have to store it somehow in that case, and it has to be callable afterwards. – PStarczewski Dec 08 '18 at 19:25
  • 1
    Possible duplicate of [C++ class member callback simple examples](https://stackoverflow.com/questions/14189440/c-class-member-callback-simple-examples) – rsjaffe Dec 08 '18 at 20:27
  • Possible duplicate of [C++11 styled callbacks?](https://stackoverflow.com/q/12338695/608639), [C++ callback using class member](https://stackoverflow.com/q/14189440/608639), etc. – jww Dec 09 '18 at 04:27
  • In you example there is no code which will call the function. so it is unclear of what you want to achieve. – AlexTheo Dec 30 '18 at 18:05
  • @AlexTheo I wanted to call function in the background when data from TCP buffer will be received via function `raise`. I`ve changed that approach as described below. – PStarczewski Dec 30 '18 at 18:08
  • @PStarczewski can you please add that call into your pseudocode? – AlexTheo Dec 30 '18 at 18:09
  • I mean where the rest_of_arguments is coming from? – AlexTheo Dec 30 '18 at 18:12
  • @AlexTheo Done, hope this is more clear explanation of rest_of_arguments background. – PStarczewski Dec 30 '18 at 18:25

1 Answers1

1

Okay, kinda late but I wanted to be sure about it and also Christmas holidays occured. Here is full example of templated class for Functors which can be later stored in map and easily called with various arguments. This is what I was talking about and I'm sure no one provided any useful advice except Some programmer dude(maybe because of my poor explanation). We use some fancy tricks here(which were mostly provided by stack overflow and documentation).

Here is sketch of class which holds all of our callbacks, we store unified callback functions by refactoring them to std::shared_ptr<void>. It is neccessary, as it performs type erasure. That allows us to store all of them in one map regardless of the arguments types. However pointer destructor will be called with proper deleter which is stored internally. More info: Stackoverflow std::shared_ptr

class Event {
 public:
  template<typename... Args>
  size_t addFunction(uint64_t callback_id, void(*callback)(Args...), Args... args) {
    std::shared_ptr<Functor<Args...>> ptr(new Functor<Args...>(this->p_input, callback, args...));

    std::map<uint64_t, std::shared_ptr<void>>::const_iterator it = p_functionMap.find(callback_id);

    if (it == p_functionMap.end()) {
      p_functionMap[callback_id] = ptr;

      return ++p_functionIndex;
    } else {
      std::cout << "WE HAVE THIS CALLBACK ALREADY!: " << callback_id << std::endl;
    }
    return p_functionIndex;
  }

  size_t removeFunction(uint64_t callback_id) {
    p_functionIndex -= p_functionMap.erase(callback_id);
    return p_functionIndex;
  }

 private:
  explicit Event(Buffer* input) :
  p_input(input) {
    p_functionIndex = 0;
  }

 private:
  std::atomic<size_t> p_functionIndex;
  std::map<uint64_t, std::shared_ptr<void>> p_functionMap;
  Buffer* p_input;
};

Here is sketch of templated class holding all neceessary information about our functor. We use parameter packs to avoid defining arguments types. Arguments are store in std::tuple which allows us to either use them later while invoking callback function as well as swap some of them with new ones(eg. OpenCL callbacks). Every essential operation on arguments can be performed inside destructor. After that callback is raised inside destructor and, well, that's all folks! More info: Stackoverflow unpack parameter packs to call function pointer

template<std::size_t I = 0, typename... Tp>
inline typename std::enable_if<I == sizeof...(Tp), void>::type
swapArguments(std::tuple<Tp...>& t, cccl::Buffer* input) { }

template<std::size_t I = 0, typename... Tp>
inline typename std::enable_if < I < sizeof...(Tp), void>::type
swapArguments(std::tuple<Tp...>& t, cccl::Buffer* input) {
  using ARG = std::remove_reference<decltype(std::get<I>(t))>::type;

  /*
    HERE NEW TUPLE AND SWAP ARGUMENTS
    OR ANYTHING ELSE(FOR EXAMPLE BUFFER DATA
    LIKE BELOW)
  */

  std::get<I>(t) = p_input->pull<ARG>();
  swapArguments<I + 1, Tp...>(t, input);
}

template<int ...> struct seq {};
template<int N, int ...S> struct gens : gens<N - 1, N - 1, S...> {};
template<int ...S> struct gens<0, S...> { typedef seq<S...> type; };

template<typename... Args>
class Functor {
  std::tuple<Args...> arguments;
  void(*callback)(Args...);
  Buffer *p_input;

public:
 void dispatchCallback()
 {
    return callbackFunction(typename gens<sizeof...(Args)>::type());
 }

 template<int ...S>
 void callbackFunction(seq<S...>)
 {
   return this->callback(std::get<S>(this->arguments) ...);
 }

 Functor(Buffer *input, void(*callback)(Args...), Args... args) {
   this->p_input = input;
   this->arguments = std::make_tuple(args...);
   this->callback = callback;

 }

 ~Functor() {
    swapArguments(this->arguments, this->p_input);
    this->dispatchCallback();
 }
};

I hope I've explained everything properly. If more detailed description is neccessary please let me know, I'll try to expand my answer. Happy new year!

PStarczewski
  • 479
  • 2
  • 17