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

Mapping a vector of one type to another using lambda

我有一些代码看起来像

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;
}

我试图使用 lambda 重写它,但代码看起来一点也不简洁或清晰:

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 中没有这样的东西(我能找到)?

你自己说了,这个map不够通用。 std::transform 另一方面 ,代价是界面更冗长。另一个原因是 map,不像 std::transform 强制新分配,这并不总是可取的。

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。笼统地表达这一点很棘手。

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

例如,Boost.Iterator 专门使用这种清晰的分离来提供您梦寐以求的每种遍历方法的整洁集合。

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

当然,没有什么能阻止您为经典 begin/end 遍历制作一个薄包装器:

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)