当前位置: 首页 > 知识库问答 >
问题:

如何将 std::函数作为函数指针传递?

宇文勇
2023-03-14

我正在尝试编写一个类模板,在内部它使用一个C函数(BFGS优化的实现,由R环境提供),接口如下:

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

其中< code>fn和< code>gr是类型的函数指针

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

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

分别是。我的< code>C 类模板如下所示:

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, ...);
    // ...
  }  
};

这样,它可以由任何具有两个指定成员函数的类进行实例化,例如:

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

这可能吗?或者也许有更好的方法来做到这一点 - 将两个成员函数分别作为函数指针传递?(如果目标函数和相应的梯度很简单,那么编写两个函数是完全可以的。但是,大多数时候,我遇到的问题要复杂得多,我必须将问题实现为一个类)。

共有3个答案

鄂坚
2023-03-14

另一种解决方案是,让optim类使用两个(可能是纯的)虚拟函数发挥其魔力,然后继承以定义一个新的类来实现它们。这看起来像

class optim {
    public:
        // ...

        virtual double fn(int n, double *par, void *ex) = 0;
        virtual void gr(int n, double *par, double *gr, void *ex) = 0;

        void minimize(arma::vec &dpar, void *ex) {
            vmmin(... , &fn, &gr, ...);
            // ...
        }
};

class Rosen : public optim {
    public:
        // ...
        double fn(int n, double *par, void *ex);
        void gr(int n, double *par, double *gr, void *ex);

    private:
        // ...
};

// main.cc    
Rosen obj;
obj.minimize(dpar, ex);
仉磊
2023-03-14
匿名用户

基本上,您需要一个具有正确签名的自由函数,获取带有“用户数据”的void*参数(没有它就无法工作),以某种方式从中提取指向std::function的指针/引用,并与其他参数一起调用它。简单的例子来说明我的意思:

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

当然,您需要确保指针保持有效!

使用下面的代码,您不需要为每个可能的签名编写这样的call_it函数。上述示例如下:

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

您的情况是:

// obj and ex passed as parameters
std::function<double(int, double *)> fn =
  [ex, &obj] (int a, double * b) { return obj.fr(a, b, ex); };
std::function<void(int, double *, double *)> 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);

我在ideone上的“划痕区”用于分叉和测试。现在,模板来拯救:

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<N>(all);
  return std::apply(Extractor{}.get_function(std::get<N>(all)),
                    arguments);
}

std::apply是一个C17版本,不过您应该可以在这个站点上轻松找到与C11兼容的版本。N指定包含“用户数据”(即指向实际函数的指针)的参数的(从零开始)索引。Extractor是一种具有静态get_functionmember函数的类型,如果给定 0.05 std::apply

单个函数的“提取器”:

struct Single {
  template<typename R, typename... Args>
  struct Extract {
    std::function<R(Args...)> & get_function(void * ptr) {
        return *(static_cast<std::function<R(Args...)>*>(ptr));
    }
  };
  template<typename R, typename... Args>
  static void * wrap(std::function<R(Args...)> & fn) {
    return &fn;
  }
};

一个用于多种功能:

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

请注意,这里< code>wrap进行分配,因此必须在< code>free_wrap_result中进行相应的取消分配。这仍然很不地道...应该转换成RAII。

tuple_remove还是需要写:

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

if_t(见下文)只是我对std的简写::需要实现条件使用忽略

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

< code>tuple_remove利用< code>std::tuple_cat接受空的< code>std::tuple

< code>std::conditional的简写:

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

庾兴发
2023-03-14

让我先说:

我不支持使用以下库

#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;
    }
};

并将其作为

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

实况示例

只是一个帮助器特征类型,它将以易于访问的形式获取任何可调用的类型

这是正在发生的事情的一半邪恶。详情请访问有状态元编程还没有形成吗?

代码的实际内容。它接受任何带有适当签名的可调用函数,并在每次调用时隐式声明一个匿名C函数,并将该可调用函数强制转换为C函数。

它有时髦的语法fnptr

get将声明与可调用对象具有相同签名的匿名C函数。

cast适用于任何兼容的可调用类型,也就是说,如果返回类型和参数是隐式可转换的,则可以强制转换。

fnptr隐式声明了在代码中被调用的每个点上的匿名C函数。它与实际上是变量的std::function不同。

如果您再次在代码中调用相同的fnptr,所有的地狱都将失败。

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

你已被警告。

 类似资料:
  • > < li> 有什么不同吗? “保存/转移”功能的最佳方式是什么?

  • 当我构建这个应用程序时,我得到错误: 我也试过: 这将错误更改为: 下面是调用这个例程的类和我传递的回调函数的示例:

  • 我创建了一个名为InputControl的类。我正在使用一个名为GLFW的库和一个函数glfwsetkeycallback。该函数定义为: E0167“void(inputcontrol::*)(GLFWwindow*window,int key,int scancode,int action,int mods)”类型的参数与“GLFWKeyFun”类型的参数不兼容

  • 问题内容: 我有以下功能: 其中,的类型是具有以下定义的接口: 题: 这是真的,和是 通过按引用 ,并且有它的价值被复制? 我认为: 是通过引用的,因为它是一张地图 是一个结构。因此,我应该传递指针以避免复制数据 问题答案: 接口类型只是一组方法。请注意,接口定义的成员未指定接收方类型是否为指针。这是因为 值类型的方法集是其关联的指针类型的方法集的子集 。满嘴 我的意思是,如果您具有以下条件: 然

  • 一个函数总是占用一段连续的内存区域,函数名在表达式中有时也会被转换为该函数所在内存区域的首地址,这和数组名非常类似。我们可以把函数的这个首地址(或称入口地址)赋予一个 指针变量,使指针变量指向函数所在的内存区域,然后通过指针变量就可以找到并调用该函数。这种指针就是 函数指针。 函数指针的定义形式为: returnType (*pointerName)(param list); returnType

  • 我正在尝试将参数传递给作为参数传递的函数指针。 代码: 我得到了这个错误: 类型"void"的参数与类型"void(*)(wchar_t*,wchar_t*)"的参数不兼容 如何解决此问题以完成我想要实现的目标? 编辑:对不起,不清楚。我实际上试图完成的是将函数注入子进程并传递两个参数(wchar_t*、wchar_t*),以便我可以使用它们。但主函数可以是void或int argc、char**