如何将参数的::std::向量绑定到函子

How do I bind a ::std::vector of arguments to a functor?

本文关键字:绑定 向量 std 参数      更新时间:2023-10-16

我正在努力使这个程序正确编译:

#include <vector>
#include <iostream>
int f(int a, int b)
{
::std::cout << "f(" << a << ", " << b << ") == " << (a + b) << 'n';
return a + b;
}
template <typename R, typename V>
R bind_vec(R (*f)(), const V &vec, int idx=0)
{
return f();
}
template <typename R, typename V, typename Arg1, typename... ArgT>
R bind_vec(R (*f)(Arg1, ArgT...), const V &vec, int idx=0)
{
const Arg1 &arg = vec[idx];
auto call = [arg, f](ArgT... args) -> R {
return (*f)(arg, args...);
};
return bind_vec(call, vec, idx+1);
}
int foo()
{
::std::vector<int> x = {1, 2};
return bind_vec(f, x);
}

理想情况下,我希望bind_vec将任意函子作为参数,而不仅仅是函数指针。其思想是在编译时从::std::vector中提取函数参数。

这不是它的最终用途,但它是我想去的地方的垫脚石。我真正要做的是生成包装函数,在编译时从future/promise类型系统中的promise中打开它们的参数。这些包装器函数本身就是承诺。

在我的最终用例中,我可以指望函子是::std::functions。但如果能知道它应该如何适用于更通用的函子,那就太好了,因为我认为这是一个广泛有趣的问题。

好的,首先,可以检测函子的arity,但这有点复杂,最好留给单独的问题。假设您将在调用中指定函子的arity。类似地,有一些方法可以获得可调用对象的返回类型,但这也超出了这个问题的范围。现在我们假设返回类型为void

所以我们想说,

call(F f, C v);

也就是说f(v[0], v[1], ..., v[n-1]),其中f具有arityn


这里有一种方法:

template <unsigned int N, typename Functor, typename Container>
void call(Functor const & f, Container const & c)
{
call_helper<N == 0, Functor, Container, N>::engage(f, c);
}

我们需要帮助:

#include <functional>
#include <cassert>
template <bool Done, typename Functor, typename Container,
unsigned int N, unsigned int ...I>
struct call_helper
{
static void engage(Functor const & f, Container const & c)
{
call_helper<sizeof...(I) + 1 == N, Functor, Container,
N, I..., sizeof...(I)>::engage(f, c);
}
};
template <typename Functor, typename Container,
unsigned int N, unsigned int ...I>
struct call_helper<true, Functor, Container, N, I...>
{
static void engage(Functor const & f, Container const & c)
{
assert(c.size() >= N);
f(c[I]...);
}
};

示例:

#include <vector>
#include <iostream>
void f(int a, int b) { std::cout << "You said: " << a << ", " << b << "n"; }
struct Func
{
void operator()(int a, int b) const
{ std::cout << "Functor: " << a << "::" << b << "n"; }
};
int main()
{
std::vector<int> v { 20, 30 };
call<2>(f, v);
call<2>(Func(), v);
}

注意:在更高级的版本中,我会用一些更多的模板机制来推导可调用对象的arity,我还会推导返回类型。然而,要想做到这一点,你需要对免费函数和各种CV限定的类成员函数进行几个专业化,所以这对这个问题来说太大了。

对于(成员)函数指针来说,这样的事情很容易实现,但对于可能过载operator()的函数来说,这会变得困难得多。如果我们假设你有一种方法来判断一个函数需要多少个参数(并假设容器实际上有那么多元素),你可以使用索引技巧将向量扩展到一个参数列表中,例如使用std::nextbegin()迭代器:

#include <utility>
#include <iterator>
template<class F, class Args, unsigned... Is>
auto invoke(F&& f, Args& cont, seq<Is...>)
-> decltype(std::forward<F>(f)(*std::next(cont.begin(), Is)...))
{
return std::forward<F>(f)(*std::next(cont.begin(), Is)...);
}
template<unsigned ArgC, class F, class Args>
auto invoke(F&& f, Args& cont)
-> decltype(invoke(std::forward<F>(f), cont, gen_seq<ArgC>{}))
{
return invoke(std::forward<F>(f), cont, gen_seq<ArgC>{});
}

这种实现对于随机访问容器来说非常好,但对于前向容器,尤其是输入容器来说就不那么好了。为了使这些功能以性能化的方式工作,您可能会尝试在每一个扩展步骤中递增迭代器,但您会遇到一个问题:函数参数的求值顺序未指定,因此您很可能会以错误的顺序传递参数。

幸运的是,有一种方法可以强制从左到右进行求值:列表初始化语法。现在,我们只需要一个可以用来传递参数的上下文,一种可能的方法是构造一个对象,通过构造函数传递函数和参数,并调用其中的函数。但是,您将失去检索返回值的能力,因为构造函数不能返回值。

我想到的是创建一个指向正确元素的迭代器数组,并在第二步中再次扩展它们,在那里它们被取消引用。

#include <utility>
template<class T> using Alias = T; // for temporary arrays
template<class F, class It, unsigned N, unsigned... Is>
auto invoke_2(F&& f, It (&&args)[N], seq<Is...>)
-> decltype(std::forward<F>(f)(*args[Is]...))
{
return std::forward<F>(f)(*args[Is]...);
}
template<class F, class Args, unsigned... Is>
auto invoke_1(F&& f, Args& cont, seq<Is...> s)
-> decltype(invoke_2(std::forward<F>(f), std::declval<decltype(cont.begin())[sizeof...(Is)]>(), s))
{
auto it = cont.begin();
return invoke_2(std::forward<F>(f), Alias<decltype(it)[]>{(void(Is), ++it)...}, s);
}
template<unsigned ArgC, class F, class Args>
auto invoke(F&& f, Args& cont)
-> decltype(invoke_1(std::forward<F>(f), cont, gen_seq<ArgC>{}))
{
return invoke_1(std::forward<F>(f), cont, gen_seq<ArgC>{});
}

该代码已根据GCC 4.7.2进行了测试,并按广告中的方式工作。


既然你说你要传递的函子是std::functions,那么获取它们的参数数量真的很容易:

template<class F> struct function_arity;
// if you have the 'Signature' of a 'std::function' handy
template<class R, class... Args>
struct function_arity<R(Args...)>
: std::integral_constant<std::size_t, sizeof...(Args)>{};
// if you only have the 'std::function' available
template<class R, class... Args>
struct function_arity<std::function<R(Args...)>>
: function_arity<R(Args...)>{};

请注意,您甚至不需要function_arity就可以使上面的invoke适用于std::function:

template<class R, class... Ts, class Args>
R invoke(std::function<R(Ts...)> const& f, Args& cont){
return invoke_1(f, cont, gen_seq<sizeof...(Ts)>{})
}

我设法做到了你想要的。最简单的解释是,如果我一开始没有推导出正确的返回类型,我稍后会展示如何添加它:

#include <vector>
#include <type_traits>
namespace {
int f(int a, int b) { return 0; }
}
template <typename ...Args>
constexpr unsigned nb_args(int (*)(Args...)) {
return sizeof...(Args);
}
template <typename F, typename V, typename ...Args>
auto bind_vec(F f, const V&, Args&& ...args)
-> typename std::enable_if<sizeof...(Args) == nb_args(F()),void>::type
{
f(std::forward<Args>(args)...);
}
template <typename F, typename V, typename ...Args>
auto bind_vec(F f, const V& v, Args&& ...args)
-> typename std::enable_if<sizeof...(Args) < nb_args(F()),void>::type
{
bind_vec(f, v, std::forward<Args>(args)..., v.at(sizeof...(Args)));
}
int main() {
bind_vec(&f, std::vector<int>(), 1);
return 0;
}

bind_vec有两个版本-如果参数包大小适合该函数,则启用一个版本。如果它仍然太小,则启用另一个。第一个版本只是使用参数包调度调用,而第二个版本则获取下一个元素(由参数包的大小决定)并递归。

SFINAE是在函数的返回类型上进行的,这样它就不会干扰类型的推导,但这意味着它需要在函数之后进行,因为它需要知道F。有一个helper函数,它可以查找调用函数指针所需的参数数量。

为了推导返回类型,我们还可以使用decltype和函数指针:

#include <vector>
#include <type_traits>
namespace {
int f(int a, int b) { return 0; }
}
template <typename ...Args>
constexpr unsigned nb_args(int (*)(Args...)) {
return sizeof...(Args);
}
template <typename F, typename V, typename ...Args>
auto bind_vec(F f, const V&, Args&& ...args)
-> typename std::enable_if<sizeof...(Args) == nb_args(F()),decltype(f(std::forward<Args>(args)...))>::type
{
return f(std::forward<Args>(args)...);
}
template <typename F, typename V, typename ...Args>
auto bind_vec(F f, const V& v, Args&& ...args)
-> typename std::enable_if<sizeof...(Args) < nb_args(F()),decltype(bind_vec(f, v, std::forward<Args>(args)..., v.at(sizeof...(Args))))>::type
{
return bind_vec(f, v, std::forward<Args>(args)..., v.at(sizeof...(Args)));
}
int main() {
bind_vec(&f, std::vector<int>(), 1);
return 0;
}