How could I pass std::function as function pointer?

前端 未结 3 1402
春和景丽
春和景丽 2021-01-15 09:23

I am trying to write a class template and internally it use a C function (implementation of BFGS optimization, provided by the R environment) with

3条回答
  •  抹茶落季
    2021-01-15 09:40

    Basically, you need a free function that has the correct signature, takes the void * parameter with the "user data" (without which it won't work), somehow extracts a pointer/reference to the std::function out of that, and calls it with the other arguments. Simple example to illustrate what I mean:

    void call_it(int value, void * user) {
      std::function * f = static_cast*>(user);
      (*f)(value);
    }
    // pass it as callback:
    registerCallback(call_it, static_cast(&my_std_function));
    

    Of course you need to make sure that the pointer remains valid!

    With the code below you don't need to write such call_it functions for every possible signature. Above example would read:

    registerCallback(trampoline<1, Single::Extract, void, int, void *>,
                     Single::wrap(my_std_function));
    

    And your case would be:

    // obj and ex passed as parameters
    std::function fn =
      [ex, &obj] (int a, double * b) { return obj.fr(a, b, ex); };
    std::function gr =
      [ex, &obj] (int a, double * b, double * c) { obj.grr(a, b, c, ex); };
    void * fns = Multi<2>::wrap(fn, gr);
    vmmin(... ,
          trampoline<2, Multi<2>::Extract<0, double, int, double *>, double, int, double *, void *>,
          trampoline<3, Multi<2>::Extract<1, void, int, double *, double *>, void, int, double *, double *, void *>,
          ..., fns, ...); // fns passed as ex
    Multi<2>::free_wrap_result(fns);
    

    My "scratch area" on ideone for forking and testing. Now, Templates to the rescue:

    template<
        std::size_t N, ///> index of parameter with the user data
        typename Extractor,
        typename R,
        typename... Args>
    R trampoline (Args... args) {
      auto all = std::make_tuple(std::ref(args)...);
      auto arguments = tuple_remove(all);
      return std::apply(Extractor{}.get_function(std::get(all)),
                        arguments);
    }
    

    std::apply is a C++17 thing, though you should be able to easily find a C++11 compatible version on this site. The N specifies the (zero based) index of the parameter which contains the "user data" (i.e. the pointer to the actual function). The Extractor is a type that has a static get_function member function, which given a void * returns something "callable" for std::apply to work with. The use case is inspired by your actual issue at hand: If you have only one pointer with "user data" which will be passed to two (or more) different callbacks, then you need a way to "extract" these different functions in the different callbacks.

    An "extractor" for a single function:

    struct Single {
      template
      struct Extract {
        std::function & get_function(void * ptr) {
            return *(static_cast*>(ptr));
        }
      };
      template
      static void * wrap(std::function & fn) {
        return &fn;
      }
    };
    

    And one for multiple functions:

    template
    struct Multi {
      template
      struct Extract {
        std::function & get_function(void * ptr) {
          auto arr = static_cast *>(ptr);
          return *(static_cast*>((*arr)[I]));
        }
      };
      template
      static void * wrap(Fns &... fns) {
        static_assert(sizeof...(fns) == Num, "Don't lie!");
        std::array arr = { static_cast(&fns)... };
        return static_cast(new std::array(std::move(arr)));
      }
      static void free_wrap_result(void * ptr) {
        delete (static_cast*>(ptr));
      }
    };
    

    Note that here wrap does an allocation, thus must be met with a corresponding de-allocation in free_wrap_result. This is still very unidiomatic ... should probably be converted to RAII.


    tuple_remove still needs to be written:

    template<
        std::size_t N,
        typename... Args,
        std::size_t... Is>
    auto tuple_remove_impl(
        std::tuple const & t,
        std::index_sequence) {
      return std::tuple_cat(if_t>::from(t)...);
    }
    template<
        std::size_t N,
        typename... Args>
    auto tuple_remove (std::tuple const & t) {
      return tuple_remove_impl(t, std::index_sequence_for{});
    }
    

    if_t (see further down) is just my shorthand for std:: conditional, Use and Ignore need to be implemented:

    struct Ignore {
      template
      static std::tuple<> from(Tuple) {
        return {};
      }
    };
    template
    struct Use {
      template
      static auto from(Tuple t) {
        return std:: make_tuple(std::get(t));
      }
    };
    

    tuple_remove exploits that std::tuple_cat accepts empty std::tuple<> arguments, and because it cannot get something out of them, basically ignores them.


    Shorthand for std::conditional:

    template
    using if_t = typename std::conditional<
        Condition, Then, Else>::type;
    

提交回复
热议问题