如何将 std::function 传递为函数指针?
How could I pass std::function as function pointer?
我正在尝试编写一个类模板,并在内部使用具有以下接口的C
函数(BFGS优化的实现,由R
环境提供):
void vmmin(int n, double *x, double *Fmin,
optimfn fn, optimgr gr, ... ,
void *ex, ... );
其中fn
和gr
是类型的函数指针
typedef double optimfn(int n, double *par, void *ex);
和
typedef void optimgr(int n, double *par, double *gr, void *ex);
分别。我的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);
这可能吗?或者也许有更好的方法可以做到这一点 - 将两个成员函数分别作为函数指针传递?(如果目标函数和对应的梯度很简单,写两个函数是绝对可以的。但是,大多数时候,我遇到的问题要复杂得多,我必须将问题作为一个类来实现)。
让我先说:
我不认可使用以下库的使用
#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));
}
现场示例
func_traits
只是一个帮助者特征类型,它将以易于访问的形式获取任何可调用对象的类型
constexpr counter
这是正在发生的事情的一半邪恶。有关详细信息,请访问有状态元编程是否尚未形成?
fnptr
代码的实际内容。它接受任何具有适当签名的可调用对象,并在调用的每个点隐式声明一个匿名 C 函数,并将可调用对象强制到 C 函数中。
它具有时髦的语法fnptr<>::get
和fnptr<>::cast<Ret(Args...)>
。这是故意的。
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
你已经被警告了。
基本上,你需要一个具有正确签名的自由函数,将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
是C++17的事情,尽管您应该能够在此站点上轻松找到C++11兼容版本。N
指定包含"用户数据"(即指向实际函数的指针)的参数的(从零开始)索引。Extractor
是一种具有静态get_function
成员函数的类型,给定一个void *
返回一些"可调用"的东西供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));
}
};
请注意,此处wrap
进行分配,因此必须在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:: conditional
、Use
和Ignore
需要实现的简写:
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));
}
};
tuple_remove
利用std::tuple_cat
接受空洞的std::tuple<>
论点,并且因为它无法从中get
出一些东西,所以基本上忽略了它们。
std::conditional
的简写:
template<bool Condition,
typename Then,
typename Else>
using if_t = typename std::conditional<
Condition, Then, Else>::type;
另一种解决方案可能是让optim
类使用两个(可能是纯的)虚函数来发挥其魔力,然后继承以定义实现它们的新类Rosen
。这可能看起来像
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);
- QMetaObject invokeMethod的基于函数指针的语法
- C++-试图将函数指针推回到另一个CPP文件中的矢量时出错
- c++r值引用应用于函数指针
- 模板函数指针和lambda
- 是否可以将llvm::FunctionType转换为C/C++原始函数指针
- 带有类的函数指针
- () 函子后面的括号,而不是函数指针?
- 全局作用域中函数指针的赋值
- 使用"Task"函数指针队列定义作业管理器
- 将成员函数指针作为参数传递给模板方法
- 如何创建对象函数指针C++映射?
- 匹配函数指针作为模板参数?
- 通过函数指针定义类范围之外的方法
- 存储在类中的函数指针
- C++从函数指针数组调用函数
- 将返回值存储在函数指针数组的指针中是如何工作的?
- 整数键映射到头文件中的成员函数指针
- 从类成员函数到类 C 函数指针的转换
- 如何将内联匿名函数分配给C++函数指针
- 将字符缓冲区强制转换为函数指针