使用lambda将一种类型的矢量映射到另一种类型

时间:2015-05-29 16:06:55

标签: c++ c++11 lambda functional-programming

我有一些看起来像

的代码
B Convert(const A& a) { 
  B b;
  // implementation omitted.
  return b;
}

vector<B> Convert(const vector<A>& to_convert) {
  vector<B> ret;
  for (const A& a : to_convert) {
    ret.push_back(Convert(a));
  }
  retun ret;
}

我试图使用lambdas重写它,但代码看起来并不简洁或更清晰:

vector<B> Convert(const vector<A>& to_convert) {
  vector<B> ret;
  std::transform(to_convert.begin(), 
                 to_convert.end(),
                 std::back_inserter(ret),
                 [](const A& a) -> B { return Convert(a); });
  retun ret;
}

我真正想做的是:

vector<B> Convert(const vector<A>& to_convert) {
  return map(to_convert, [](const A& a) -> B { return Convert(a); });
}

其中map是一个功能样式映射函数,可以实现为:

template<typename T1, typename T2>
vector<T2> map(const vector<T1>& to_convert, 
               std::function<T2(const T1&)> converter) {
  vector<T2> ret;
  std::transform(to_convert.begin(), 
                 to_convert.end(),
                 std::back_inserter(ret),
                 converter);
  retun ret;
}

显然上面是有限的,因为它只适用于vector,理想情况下,人们会想要所有容器类型的类似功能。在一天结束时,上述情况仍然不比我原来的代码好。

为什么stl中没有这样的东西(我能找到)?

3 个答案:

答案 0 :(得分:0)

你自己说过,这个map不够通用。另一方面,{em>是 ,代价是更详细的界面。另一个原因是std::transformmap不同,强制新的分配,这并不总是令人满意。

答案 1 :(得分:0)

template<class F, class R, class Out>
struct convert_t {
  F f;
  R r;
  // TODO: upgrade std::begin calls with Koenig lookup
  template<class D>
  operator D()&&{
    D d;
    std::transform(
      std::begin(std::forward<R>(r)),
      std::end(std::forward<R>(r)),
      std::back_inserter(d),
      std::forward<F>(f)
    );
    return d;
  }
  template<template<class...>class Z, class Result=Z<
    typename std::decay<Out>::type
  >>
  Result to()&&{return std::move(*this);}
};
template<class F, class R,
    class dF=typename std::decay<F>::type,
    class dR=typename std::decay<R>::type,
    class R_T=decltype(*std::begin(std::declval<dR>())),
    class Out=typename std::result_of<dF&(R_T)>::type
>
convert_t<dF,dR,Out>
convert( F&& f, R&& r ) { return {std::forward<F>(f), std::forward<R>(r)}; }

给了我们这个:

std::vector<int> vec{1,2,3};
auto r = convert(Convert, vec).to<std::vector>();
for (auto&& x:r)
    std::cout << x << '\n';
std::vector<double> r2 = convert(Convert, vec);
for (auto&& x:r)
    std::cout << x << '\n';

live example

这只处理序列容器输出,因为std::back_inserter必须交换std::inserter或者某些关联容器。

此外,一些关联容器(如map)不喜欢传递pair - 他们想要Key,Value。表达一般是棘手的。

答案 2 :(得分:0)

标准库负责分隔容器及其遍历。通过让std算法直接获取容器,您将失去为不同遍历方法使用不同迭代器类型的可能性。

例如,Boost.Iterator特别使用这种干净的分离来提供你梦寐以求的每种遍历方法的整洁集合。

另请注意,并非所有迭代器都遍历实际容器:std::back_inserter(如果您不想进入未分配的空间,则应使用ret.begin()而不是实际构建容器)并且std::ostream_iterator与任何容器完全无关,因为它将分配给它的内容推送到流中。

当然,没有什么可以阻止你为经典的开始/结束遍历制作一个瘦的包装器:

template <
    template <class...> class Container,
    class Transform,
    class ContainerT,
    class... ContainerParams
>
auto map(Container<ContainerT, ContainerParams...> const &container, Transform &&transform) {
    using DestT = std::result_of_t<Transform(ContainerT const&)>;
    Container<DestT, ContainerParams...> res;

    using std::begin;
    using std::end;
    std::transform(
        begin(container),
        end(container),
        std::inserter(res, end(res)),
        std::forward<Transform>(transform)
    );

    return res;
}

(live on Coliru)