4

我正在尝试使该程序正确编译:

#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这个想法是在编译时从 a 中提取函数参数。

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

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

4

3 回答 3

5

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

所以我们想说,

call(F f, C v);

那应该说f(v[0], v[1], ..., v[n-1]),哪里f有arity 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);
}

注意:在更高级的版本中,我会使用更多的模板机制来推断可调用对象的数量,并且还会推断出返回类型。但是,要使其工作,您将需要对自由函数和各种 CV 限定的类成员函数进行一些专门化,因此对于这个问题来说,这将变得太大。

于 2012-11-27T23:37:56.090 回答
4

对于(成员)函数指针来说,这样的事情很容易实现,但对于可能重载的仿函数来说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_arityinvoke上面的工作中进行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)>{})
}
于 2012-11-27T23:46:19.540 回答
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;
}
于 2012-11-27T23:38:55.620 回答