我正在处理一个提供这种形式的钩子(Hook)的 C 系统:
int (*EXTENSIONFUNCTION)(NATIVEVALUE args[]);
可以注册一个 EXTENSIONFUNCTION 和它需要的参数数量。
我的想法是创建一个类 Extension
来包装一个扩展。它可以从 std::function (或者任何 Callable,理想情况下,但我们现在只说它包含一个 std::function) 构造。扩展采用 Value 参数,这些参数包含 NATIVEVALUE (但更大)。例如,我会使用 sizeof...(Ts)
自动处理参数计数。它可能看起来像这样:
Extension<lib::Integer, lib::String> foo =
[](lib::Integer i, lib::String s) -> int {
std::cout << i;
std::cout << s;
return 0;
}
问题是,为了让 C 库注册和调用它,它需要基于数组的接口(interface)。 :-/
我开始尝试让编译器编写一些填充程序,但我没有找到实现它的方法。我可以在 Extension 上有一个可变的 operator()
,并在 NATIVEVALUE 上执行运行时循环以获得 Value[] 的数组。但是我该怎么办呢?我不能用它调用 std::function。
所以我似乎需要制作一个调用我的 std::function 的 EXTENSIONFUNCTION 实例,作为每个扩展实例的成员。
但基本上我发现自己遇到了一个问题,因为我有一个用于扩展的可变参数模板类...然后在采用此 NATIVEVALUE args[ 方面有点“无法从这里到达那里” ]
并能够使用它们调用 std::function。如果 std::function 愿意用 std::array 参数调用,那将解决它,但当然不是它的工作方式。
是否可以构建这种类型的垫片?我能做的“丑陋”的事情就是代理另一个数组,比如:
Extension<2> foo =
[](lib::Value args[]) -> int {
lib::Integer i (args[0]);
lib::String s (args[1]);
std::cout << i;
std::cout << s;
return 0;
}
但这并不符合人体工程学。这似乎是不可能的,不知道调用约定和做某种内联汇编的东西来处理参数和调用函数(甚至这只适用于函数,而不是一般的 Callables)。但是这里的人以前已经证明了不可能的可能,通常是通过“那不是你想要的,你真正想要的是……”
更新:我刚发现这个,这看起来很有希望......我仍在努力消化它的相关性:
"unpacking" a tuple to call a matching function pointer
( 注意:在我的目标中有一些交叉问题。另一点是来自 lambda 的类型推断。这里的答案似乎是最好的选择......它似乎有效,但我不知道它是否符合“犹太洁食”:Initialize class containing a std::function with a lambda )
最佳答案
如果我设法将问题简化为最简单的形式,您需要一种方法来调用 std::function
从固定大小的 C 样式数组中获取其参数,而无需创建运行时循环。那么,这些功能可能会解决您的问题:
template<std::size_t N, typename T, typename F, std::size_t... Indices>
auto apply_from_array_impl(F&& func, T (&arr)[N], std::index_sequence<Indices...>)
-> decltype(std::forward<F>(func)(arr[Indices]...))
{
return std::forward<F>(func)(arr[Indices]...);
}
template<std::size_t N, typename T, typename F,
typename Indices = std::make_index_sequence<N>>
auto apply_from_array(F&& func, T (&arr)[N])
-> decltype(apply_from_array_impl(std::forward<F>(func), arr, Indices()))
{
return apply_from_array_impl(std::forward<F>(func), arr, Indices());
}
这是一个演示如何使用它的示例:
auto foo = [](int a, int b, int c)
-> int
{
return a + b + c;
};
int main()
{
Value arr[] = { 1, 2, 3 };
std::cout << apply_from_array(foo, arr); // prints 6
}
当然,对于签名 int (*)(T args[])
,args
只是一个 T*
而你不需要' 在编译时知道它的大小。但是,如果您从其他地方知道编译时间大小(例如从 std::function
),您仍然可以调整 apply_from_array
以手动给出编译时间大小信息:
template<std::size_t N, typename T, typename F, std::size_t... Indices>
auto apply_from_array_impl(F&& func, T* arr, std::index_sequence<Indices...>)
-> decltype(std::forward<F>(func)(arr[Indices]...))
{
return std::forward<F>(func)(arr[Indices]...);
}
template<std::size_t N, typename T, typename F,
typename Indices = std::make_index_sequence<N>>
auto apply_from_array(F&& func, T* arr)
-> decltype(apply_from_array_impl<N>(std::forward<F>(func), arr, Indices()))
{
return apply_from_array_impl<N>(std::forward<F>(func), arr, Indices());
}
然后像这样使用函数:
int c_function(NATIVEVALUE args[])
{
return apply_from_array<arity>(f, args);
}
在上面的例子中,考虑 f
是一个 std::function
而 arity
是 f
是您在编译时设法以某种方式获得的。
注意:我使用了 C++14 std::index_sequence
和 std::make_index_sequence
但如果您需要您的代码工作使用 C++11,您仍然可以在您链接的我的旧问题中使用手工制作的等价物,例如 indices
和 make_indices
。
后果:问题是关于 real code ,当然比上面稍微复杂一点。扩展机制的设计使得每次调用扩展函数时,C++ 代理 C API(lib::Integer
、lib::String
等...)是动态创建的,然后传递给用户定义的函数。这需要一个新方法,Extension
中的 applyFunc
:
template<typename Func, std::size_t... Indices>
static auto applyFuncImpl(Func && func,
Engine & engine,
REBVAL * ds,
utility::indices<Indices...>)
-> decltype(auto)
{
return std::forward<Func>(func)(
std::decay_t<typename utility::type_at<Indices, Ts...>::type>{
engine,
*D_ARG(Indices + 1)
}...
);
}
template <
typename Func,
typename Indices = utility::make_indices<sizeof...(Ts)>
>
static auto applyFunc(Func && func, Engine & engine, REBVAL * ds)
-> decltype(auto)
{
return applyFuncImpl(
std::forward<Func>(func),
engine,
ds,
Indices {}
);
}
applyFunc
接受函数来调用一个使用适当类型(Integer
、String
等...)的实例调用它的函数来自使用 Engine&
和 REBVAL*
即时创建的底层 C API。
关于c++ - 将 std::function 代理到需要参数数组的 C 函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27519937/