我正在努力使这个程序正确编译:
#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
中提取函数参数。
这不是最终用途,但它是我想去的地方的垫脚石。我真正在做的是生成包装器函数,它们在编译时从未来/ promise类型系统中的promises中解包它们的参数。这些包装函数本身就是承诺。
在我的最终用例中,我可以依靠::std::function
的仿函数。但是如果能够了解它如何适用于更一般的仿函数,那将是很好的,因为我认为这是一个广泛有趣的问题。
答案 0 :(得分:5)
好的,首先,可以完成检测仿函数的arity,但它有点牵扯,最好留给一个单独的问题。假设您将在调用中指定仿函数的arity。类似地,有一些方法可以获得可调用对象的返回类型,但这也超出了这个问题的范围。我们现在假设返回类型为void
。
所以我们想说,
call(F f, C v);
应该说f(v[0], v[1], ..., v[n-1])
,其中f
有n
。
这是一种方法:
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限定类成员函数的特化,所以这对于这个问题来说太大了。
答案 1 :(得分:4)
对于(成员)函数指针来说,这样的东西很容易实现,但对于可能过载operator()
的仿函数,这会让事情变得更加艰难。如果我们假设你有办法告诉一个函数需要多少个参数(并假设容器实际上有那么多元素),你可以使用indices trick将向量扩展到参数列表中,例如使用std::next
和begin()
迭代器:
#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::function
s,那么获取参数的数量非常简单:
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)>{})
}
答案 2 :(得分:1)
我设法做你想做的事。最简单的解释一下,如果我将其保留为不首先推断出正确的返回类型,我将在稍后展示如何添加:
#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
。有一个辅助函数可以找到调用函数指针所需的参数数量。
为了推断返回类型,我们也可以将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;
}