使用C ++中的参数包进行函数式编程

时间:2015-12-10 16:35:44

标签: c++ templates c++11 c++14 template-meta-programming

这是我所遇到的另一个问题的简化,但它本身就很好。我们的想法是在Scheme中实现类似于mapapply的功能原语。

回顾一下:在Scheme中,给定一个函数f(apply f '(1 2 3))相当于(f 1 2 3)(map f '(1 2 3))相当于((f 1) (f 2) (f 3))

实施apply很容易,还有很多其他问题可以说明如何做到这一点:

template <class Func, class... Args, std::size_t... Ixs>
auto apply_helper(Func&& func, const tuple<Args...>& args,
                  index_sequence<Ixs...>)
    -> decltype(func(get<Ixs>(args)...))
{
  return forward<Func>(func)(get<Ixs>(forward<const tuple<Args...>&>(args))...);
}

template <class Func, class... Args,
          class Ixs = make_index_sequence<sizeof...(Args)>>
auto apply(Func&& func, const tuple<Args...>& args)
    -> decltype(apply_helper(func, args, Ixs()))
{
  return apply_helper(forward<Func>(func),
                      forward<const tuple<Args...>&>(args), Ixs());
}

void print3(int x, const char* s, float f) {
  cout << x << "," << s << "," << f << endl;
}

int main() {
  auto args = make_tuple(2, "Hello", 3.5);
  apply(print3, args);
}

现在实施map,这有点棘手。我们希望这样的内容有效,所以这就是目标(这里使用mapcar来避免与std::map发生冲突):

template <class Type>
bool print1(Type&& obj) {
  cout << obj;
  return true;
}

int main() {
  auto args = make_tuple(2, "Hello", 3.5);
  mapcar(print1, args);
}

传递print1函数的其他替代方法也可以。

因此,如果我们对该函数进行硬编码,则以下代码将正常工作:

template <class... Args, std::size_t... Ixs>
auto mapcar_helper(const tuple<Args...>& args,
                   index_sequence<Ixs...>)
    -> decltype(make_tuple(print1(get<Ixs>(args))...))
{
  return make_tuple(print1(get<Ixs>(forward<const tuple<Args...>&>(args)))...);
}

template <class... Args,
          class Ixs = make_index_sequence<sizeof...(Args)>>
auto mapcar(const tuple<Args...>& args)
    -> decltype(mapcar_helper(args, Ixs()))
{
  return mapcar_helper(forward<const tuple<Args...>&>(args), Ixs());
}

问题是如何将这段代码概括为接受任意名称作​​为输入并让它解析模板中的名称查找?只是添加模板参数不起作用,因为它无法解决此时的函数重载。

我们希望调用mapcar以上的代码代码:

make_tuple(print1(2), print1("Hello"), print1(3.5));

更新:最初的挑战之一是让它与C ++ 11编译器一起使用,部分原因是因为我使用的是GCC 4.8,还因为我想调查一下如何使用它。基于这些注释,下面是一个如何在没有多态lambda(需要C ++ 14编译器支持)的帮助下完成它的示例。

它并不像我想的那样简单,C ++ 14的功能会让它变得如此简单,但至少它可以为用户带来轻微的不便。

template <class Func, class... Args, std::size_t... Ixs>
auto mapcar_helper(Func&& func, const tuple<Args...>& args,
                   index_sequence<Ixs...>)
    -> decltype(make_tuple(func(get<Ixs>(args))...))
{
  return make_tuple(func(get<Ixs>(args))...);
}

template <class Func, class... Args,
          class Ixs = make_index_sequence<sizeof...(Args)>>
auto mapcar(Func&& func, const tuple<Args...>& args)
   -> decltype(mapcar_helper(func, args, Ixs())
{
  return mapcar_helper(forward<Func>(func), forward<decltype(args)>(args), Ixs());
}

为了能够传递模板“function”,我们需要将它包装在一个对象中:

struct print1 {
  template <class Type> const Type& operator()(Type&& obj) {
    std::cout << obj << " ";
    return obj;
  }
};

现在可以将其传递给函数,类型查找将在参数包扩展时完成:

   mapcar(print1(), make_tuple(2, "Hello", 3.5));

2 个答案:

答案 0 :(得分:3)

template <typename F, class... Args, std::size_t... Ixs>
auto mapcar_helper(F f, const tuple<Args...>& args,
                   index_sequence<Ixs...>)
    -> decltype(make_tuple(f(get<Ixs>(args))...))
{
  return make_tuple(f(get<Ixs>(args))...);
}

template <typename F, class... Args,
          class Ixs = make_index_sequence<sizeof...(Args)>>
auto mapcar(F f, const tuple<Args...>& args)
    -> decltype(mapcar_helper(move(f), args, Ixs()))
{
  return mapcar_helper(move(f), args, Ixs());
}

然后你做:

mapcar([](auto&& obj) { return print1(std::forward<decltype(obj)>(obj)); }, args);

也许我不明白这个问题。你需要将print1包装在lambda中,否则它是不明确的;您希望传递print1的哪个实例?

如果你没有大恐怖症,你可以使用宏来使它更优雅:

#define LIFT(F) ([&](auto&&... args) -> decltype(auto) { \
    return F(::std::forward<decltype(args)>(args)...);  \
})

然后您可以使用mapcar(LIFT(print1), args)

这就是我编写自己的map函数的方法:

template<typename F, class Tuple, std::size_t... Is>
auto map(Tuple&& tuple, F f, std::index_sequence<Is...>)
{
    using std::get;
    return std::tuple<decltype(f(get<Is>(std::forward<Tuple>(tuple))))...>{
        f(get<Is>(std::forward<Tuple>(tuple)))...
    };
}

template<typename F, class Tuple>
auto map(Tuple&& tuple, F f)
{
    using tuple_type = std::remove_reference_t<Tuple>;
    std::make_index_sequence<std::tuple_size<tuple_type>::value> seq;
    return (map)(std::forward<Tuple>(tuple), std::move(f), seq);
}

答案 1 :(得分:3)

我错过了什么?

#include <iostream>
#include <string>


template<class F, class...Args>
void map(F&& f, Args&&...args)
{
    using expander = int[];
    (void) expander { 0, ((void) f(args), 0)... };
}

auto main() -> int
{
    using namespace std;

    map([](const auto& x) { cout << x << endl; }, 1, "hello"s, 4.3);

    return 0;
}

预期产出:

1
hello
4.3

请注意,在c ++ 17中,map()函数变得更令人愉悦:

template<class F, class...Args>
void map(F&& f, Args&&...args)
{
    (f(args), ...);
}

如果你的下一个问题是&#34;为什么括号?&#34;。答案是因为 fold表达式仅在表达式的上下文中进行评估。 f(arg1), f(arg2);是一个声明。

参考:http://en.cppreference.com/w/cpp/language/fold