标准::转换的泛化

Generalization of std::transform

本文关键字:泛化 转换 标准      更新时间:2023-10-16

考虑一下我为 N 个输入迭代器编写的 std::transform 的简单概括:

#include <iostream>
#include <vector>
#include <string>
template <typename InputIterator, typename OutputIterator, typename NaryOperator, typename... InputIterators>
OutputIterator transform (InputIterator first, InputIterator last, OutputIterator result,
NaryOperator op, InputIterators... iterators) {
    while (first != last) {
        *result = op(*first, *iterators++...);
        ++result;  ++first;
    }
    return result;
}
int main() {
    const std::vector<int> a = {1,2,3,4,5};
    const std::vector<double> b = {1.2, 4.5, 0.6, 2.8, 3.1};
    const std::vector<std::string> c = {"hi", "howdy", "hello", "bye", "farewell"};
    std::vector<double> result(5);
    transform (a.begin(), a.end(), result.begin(),
        [](int i, double d, const std::string& s)->double {return i + d + s.length();},
        b.begin(), c.begin());
    for (double x : result) std::cout << x << ' ';  // 4.2 11.5 8.6 9.8 16.1
}

我现在要做的是允许向量abc具有不同的长度(并且可以删除参数InputIterator last(,在这种情况下,transform将继续变换,直到最长的向量用完,使用较短向量的默认值。

我认为这只是调整transform函数中所有短容器的大小的问题,但是transform的参数没有给出有关所有容器长度的信息。 有没有办法在transform内计算每个容器的长度,从而获得最大长度,从而填充较短容器的默认值? 理想情况下,仅使用以下语法:

transform (OutputIterator result, NaryOperator op, InputIterators... iterators);

更新:按照拉玛纳的想法,我正在考虑使用类似的东西:

template <typename OutputIterator, typename NaryOperator, typename... InputIterators>
OutputIterator transform (OutputIterator result, NaryOperator op, InputIterators... first, InputIterators... last) {
    while (true) {
        *result = op((first == last ?
            typename std::iterator_traits<InputIterators>::value_type() : *first++)...);
        ++result;
    }
    return result;
}

transform (result.begin(),
    [](int i, double d, const std::string& s)->double {return i + d + s.length();},
    a.begin(), b.begin(), c.begin(), a.end(), b.end(), c.end());

不编译。我想是因为编译器不知道last...从哪里开始。

所以我接下来尝试了这个:

template <typename OutputIterator, typename NaryOperator, typename... InputIteratorsPairs>
OutputIterator transform (OutputIterator result, NaryOperator op, InputIteratorsPairs... pairs) {
    while (true) {
        *result = op((pairs.first == pairs.second ?
            typename InputIteratorsPairs::first_type() : *pairs.first++)...);
        ++result;
    }
    return result;
}

transform_ (result.begin(),
    [](int i, double d, const std::string& s)->double {return i + d + s.length();},
    std::make_pair(a.begin(), a.end()), std::make_pair(b.begin(), b.end()), std::make_pair(c.begin(), c.end()));

也不编译(反正我不喜欢语法(。

#include <cstddef>
#include <utility>
#include <tuple>
#include <iterator>
bool all(bool a)
{
    return a;
}
template <typename... B>
bool all(bool a, B... b)
{
    return a && all(b...);
}
template <typename OutputIterator, typename NaryOperator, typename... InputIterators, std::size_t... Is>
OutputIterator transform(OutputIterator result, NaryOperator op, std::index_sequence<Is...>, InputIterators... iterators)
{
    auto tuple = std::make_tuple(iterators...);
    while (!all(std::get<2*Is>(tuple) == std::get<2*Is + 1>(tuple)...))
    {
        *result = op((std::get<2*Is>(tuple) != std::get<2*Is + 1>(tuple)
              ? *std::get<2*Is>(tuple)++
              : typename std::iterator_traits<typename std::tuple_element<2*Is, decltype(tuple)>::type>::value_type{})...);
        ++result;
    }
    return result;
}
template <typename OutputIterator, typename NaryOperator, typename... InputIterators>
OutputIterator transform(OutputIterator result, NaryOperator op, InputIterators... iterators)
{
    return transform(result, op, std::make_index_sequence<sizeof...(InputIterators)/2>{}, iterators...);
}

测试:

int main()
{
    const std::vector<int> a = {1,2,3,4,5};
    const std::vector<double> b = {1.2, 4.5, 0.6, 2.8, 3.1};
    const std::vector<std::string> c = {"hi", "howdy", "hello", "bye", "farewell"};
    std::vector<double> result(5);
    transform(result.begin(),
        [] (int i, double d, const std::string& s) -> double
        {
            return i + d + s.length();
        },
        a.begin(), a.end(),
        b.begin(), b.end(),
        c.begin(), c.end());
    for (double x : result) std::cout << x << ' ';
}

输出:

4.2 11.5 8.6 9.8 16.1 

演示

pairs.first == pairs.second ?
        typename InputIteratorsPairs::first_type() : *pairs.first++

您正在对迭代器进行值初始化,而不是迭代器指向的类型:而不是迭代器指向的类型。此外,您还有一个无限循环和未定义的行为,因为您不断递增result。以下是修复这些问题的版本(需要<algorithm>,不一定是最有效的:

bool any(std::initializer_list<bool> vs)
{
    return std::any_of(begin(vs), end(vs), [](bool b) { return b; });
}
template<typename OutputIterator, typename NaryOperator, typename... InputIteratorsPairs>
OutputIterator transform(OutputIterator result, NaryOperator op, InputIteratorsPairs... pairs) {
    while (any({(pairs.first != pairs.second)...})) {
        *result = op((pairs.first == pairs.second ?
            typename InputIteratorsPairs::first_type::value_type() : *pairs.first++)...);
        ++result;
    }
    return result;
}

这是一个基于范围的解决方案。 我们不是在迭代器上运行,而是在一系列迭代器上运行。

范围是一对带有一些帮助程序的迭代器。 这是一个最小的实现,只编写了一些帮助程序:

template<class It> using it_value_type =
  typename std::iterator_traits<It>::value_type;
template<class It> using it_reference =
  typename std::iterator_traits<It>::reference;
template<class It>
struct range_t {
  It b, e;
  range_t():b(),e(){}
  range_t(It s, It f):b(s),e(f){}
  template<class C, class=std::enable_if_t<!std::is_same<std::decay_t<C>,range_t>{}>>
  range_t(C&& c):
    range_t(std::begin(c),std::end(c))
  {}
  It begin() const { return b; }
  It end() const { return e; }
  bool empty() const { return begin()==end(); }
  it_reference<It> front() const { return *begin(); }
  it_reference<It> back() const { return *std::prev(end()); }
  range_t pop_front() const {
    if (empty()) return {};
    else return {std::next(begin()), end()};
  }
};

使创建range_t更容易的函数:

template<class C, class It=std::decay_t<decltype(std::begin(std::declval<C&>()))>>
range_t<It> range( C&& c ) {
  return {std::begin(c), std::end(c)};
}

一个帮助程序,可以更轻松地检查一堆bool以查看它们是否都是真的:

bool all_of( std::initializer_list<bool> il ) {
  return std::all_of( il.begin(), il.end(), [](bool b){return b;} );
}

现在开始工作。 转换优先的基于范围的实现:

template<class Sink, class Operator, class...Its>
Sink transform( Sink sink, Operator op, range_t<Its>... srcs ) {
  while(!all_of({srcs.empty()...})) {
    *sink++ = op( (srcs.empty()?it_value_type<Its>{}:srcs.front())... );
    using discard=int[];
    (void)discard{0,
      ((srcs = srcs.pop_front()),0)...
    };
  }
  return sink;
}

以及非基于范围的实现,它只是转发到上面:

template<class Sink, class Operator, class...Srcs>
Sink transform( Sink sink, Operator op, Srcs&&... srcs ) {
  return transform( sink, op, range(srcs)... );
}

它们应该能够作为彼此的重载存在。

第一个范围结束时将其切换为停止很容易 - 将all_of换成any_of

活生生的例子。