Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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 the following interface:

void vmmin(int n, double *x, double *Fmin, 
           optimfn fn, optimgr gr, ... ,
           void *ex, ... );

where fn and gr are function pointers of type

typedef double optimfn(int n, double *par, void *ex);

and

typedef void optimgr(int n, double *par, double *gr, void *ex);

respectively. My C++ class template looks like this:

template <typename T>
class optim {
 public:
  // ...
  void minimize(T& func, arma::vec &dpar, void *ex) {
    std::function<optimfn> fn = 
      std::bind(&T::fr, func, std::placeholders::_1, 
                std::placeholders::_2, std::placeholders::_3);
    std::function<optimgr> gr = 
      std::bind(&T::grr, func, std::placeholders::_1,
                std::placeholders::_2, std::placeholders::_3,
                std::placeholders::_4);
    // ERROR: cannot convert std::function to function pointer
    vmmin(... , fn, gr, ...);
    // ...
  }  
};

so that it can be instantiated by any class with two specified member functions, e.g.:

class Rosen {
 public:
  // ...
  double fr(int n, double *par, void *ex);
  void grr(int n, double *par, double *gr, void *ex);
 private:
  // ...
};

// main.cc
Rosen func;
optim<Rosen> obj;
obj.minimize(func, dpar, ex);

Is this possible? Or maybe there is a better way of doing this -- pass the two member functions separately as function pointer? (If the objective function and the corresponding gradient are simple, it is absolutely okay to write two functions. However, most of the time, the problem I got is far more complicated and I have to implement the problem as a class).

like image 795
ypan1988 Avatar asked Sep 15 '26 20:09

ypan1988


1 Answers

Let me say up front:

I do not endorse the usage of the following library

#include<tuple>
#include<type_traits>
#include<utility>

// func_traits
template <typename T>
struct func_traits : public func_traits<decltype(&std::remove_reference_t<T>::operator())> {};

template <typename Callable, typename Ret, typename... Args>
struct func_traits<Ret(Callable::*)(Args...) const> {
    using ptr_type = Ret (*) (Args...);
    using return_type =  Ret;

    template<std::size_t i>
    struct arg
    {
        using type = typename std::tuple_element<i, std::tuple<Args...>>::type;
    };

    template<typename Ret2>
    using cast_return_type = Ret2 (*) (Args...);
};

template<typename Ret, typename... Args>
struct func_traits<Ret (&) (Args...)> : public func_traits<Ret (*) (Args...)> {};

template <typename Ret, typename... Args>
struct func_traits<Ret (*) (Args...)>
{
    using ptr_type = Ret (*) (Args...);
    using return_type =  Ret;

    template<std::size_t i>
    struct arg
    {
        using type = typename std::tuple_element<i, std::tuple<Args...>>::type;
    };

    template<typename Ret2>
    using cast_return_type = Ret2 (*) (Args...);
};



// constexpr counter
template <int N>
struct flag
{
    friend constexpr int adl_flag(flag<N>);
    constexpr operator int() { return N; }
};

template <int N>
struct write
{
    friend constexpr int adl_flag(flag<N>) { return N; }
    static constexpr int value = N;
};

template <int N, int = adl_flag(flag<N>{})>
constexpr int read(int, flag<N>, int R = read(0, flag<N + 1>{}))
{
    return R;
}

template <int N>
constexpr int read(float, flag<N>)
{
    return N;
}

template <int N = 0>
constexpr int counter(int R = write<read(0, flag<N>{})>::value)
{
    return R;
}


// fnptr
template<int nonce = counter()>
class fnptr
{
    //these are to make sure fnptr is never constructed
    //technically the first one should be enough, but compilers are not entirely standard conformant
    explicit fnptr() = delete;
    fnptr(const fnptr&) {}
    ~fnptr() = delete;

    template<typename Callable, typename Ret, typename... Args>
    static auto cast(Callable&& c, Ret(*fp)(Args...)) -> decltype(fp)
    {
        using callable_type = std::remove_reference_t<Callable>;
        static callable_type clb{std::forward<Callable>(c)};
        static bool full = false;
        if(full)
        {
            clb.~callable_type();
            new (&clb) decltype(clb){std::forward<Callable>(c)};
        }
        else
            full = true;
        return [](Args... args) noexcept(noexcept(clb(std::forward<Args>(args)...))) -> Ret
        {
            return Ret(clb(std::forward<Args>(args)...));
        };
    }

public:
    template<typename Signature, typename Callable>
    static Signature* cast(Callable&& c)
    {
        return cast(std::forward<Callable>(c), static_cast<Signature*>(nullptr));
    }

    template<typename Signature, typename Ret, typename... Args>
    static auto cast(Ret (*fp)(Args...))
    {
        static decltype(fp) fnptr;
        fnptr = fp;
        using return_type = typename func_traits<Signature*>::return_type;
        return [](Args... args) noexcept(noexcept(fp(std::forward<Args>(args)...)) -> return_type
        {
            return return_type(fnptr(std::forward<Args>(args)...));
        };
    }

    template<typename Callable>
    static auto get(Callable&& c)
    {
        return cast(std::forward<Callable>(c), typename func_traits<Callable>::ptr_type{nullptr});
    }

    template<typename Ret, typename... Args>
    static auto get(Ret (*fp)(Args...))
    {
        return fp;
    }
};

And use it as

#include<functional>
#include<iostream>

using optimfn = double (int, double*, void*);
using optimgr = void (int, double*, double*, void*);

void test(optimfn* fn, optimgr* gr)
{
    double d;
    fn(42, &d, &d);
    gr(42, &d, &d, &d);
}

int main()
{
    std::function<optimfn> fn = [](int, double*, void*){
        std::cout << "I'm fn" << std::endl;
        return 0.;
    };
    std::function<optimgr> gr = [](int, double*, double*, void*){
        std::cout << "I'm gr" << std::endl;
    };

    test(fnptr<>::get(fn), fnptr<>::get(gr));
}

Live example

func_traits

Is just a helper traits type that will fetch the type of any callable in an easily accessible form

constexpr counter

This is half the evilness of what's going on. For details visit is stateful metaprogramming ill formed yet?

fnptr

The actual meat of the code. It takes any callable with appropriate signatures and implicitly declares an anonymous C function at every point it is called and coerces the callable into the C function.

It has the funky syntax fnptr<>::get and fnptr<>::cast<Ret(Args...)>. This is intentional.

get will declare the anonymous C function with the same signature as the callable object.

cast works on any compatible callable type, that is, if the return type and arguments are implicitly convertible, it can be casted.

Caveats

fnptr implicitly declares an anonymous C function at each point in the code it is called. It is not the same as std::function that is actually a variable.

If you call the same fnptr in the code again, all hell breaks lose.

std::vector<int(*)()> v;
for(int i = 0; i < 10; i++)
    v.push_back(fnptr<>::get([i]{return i;}));  // This will implode

You have been warned.

like image 155
Passer By Avatar answered Sep 18 '26 08:09

Passer By



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!