Home > Software engineering >  How to write iterator wrapper that transforms several values from base container
How to write iterator wrapper that transforms several values from base container

Time:11-13

I have algorithm that uses iterators, but there is a problem with transforming values, when we need more than single source value. All transform iterators just get some one arg and transforms it. (see similar question from the past) Code example:

template<typename ForwardIt>
double some_algorithm(ForwardIt begin, ForwardIt end) {
    double result = 0;
    for (auto it = begin; it != end;   it) {
        double t = *it;
        /*
            do some calculations..
        */
        result  = t;
    }
    return result;
}

int main() {
    {
        std::vector<double> distances{ 1, 2, 3, 4 };
        double t = some_algorithm(distances.begin(), distances.end());
        std::cout << t << std::endl;
        /* works great */
    }

    {
        /* lets now work with vector of points.. */
        std::vector<double> points{ 1, 2, 4, 7, 11 };

        /* convert to distances.. */
        std::vector<double> distances;
        distances.resize(points.size() - 1);
        for (size_t i = 0; i   1 < points.size();   i)
            distances[i] = points[i   1] - points[i];

        /* invoke algorithm */
        double t = some_algorithm(distances.begin(), distances.end());
        std::cout << t << std::endl;
    }
}

Is there a way (especialy using std) to create such an iterator wrapper to avoid explicitly generating distances value? It could be fine to perform something like this:

template<typename BaseIterator, typename TransformOperator>
struct GenericTransformIterator {
    GenericTransformIterator(BaseIterator it, TransformOperator op) : it(it), op(op) {}

    auto operator*() {
        return op(it);
    }

    GenericTransformIterator& operator  () {
          it;
        return *this;
    }

    BaseIterator it;
    TransformOperator op;

    friend bool operator!=(GenericTransformIterator a, GenericTransformIterator b) {
        return a.it != b.it;
    }
};

and use like:

{
    /* lets now work with vector of points.. */
    std::vector<double> points{ 1, 2, 4, 7, 11 };

    /* use generic transform iterator.. */
    /* invoke algorithm */
    auto distance_op = [](auto it) {
        auto next_it = it;
          next_it;
        return *next_it - *it;
    };
    double t = some_algorithm(
        generic_transform_iterator(points.begin(), distance_op), 
        generic_transform_iterator(points.end() -1 , distance_op));
    std::cout << t << std::endl;
}

So general idea is that transform function is not invoked on underlying object, but on iterator (or at least has some index value, then lambda can capture whole container and access via index).

I used to use boost which has lot of various iterator wrapping class. But since cpp20 and ranges I'm curious if there is a way to use something existing from std:: rather than writing own wrappers.

CodePudding user response:

I'm not sure this addresses your problem (let me know if it doesn't and I'll remove the answer), but you may be able to achieve that with ranges (unfortunately, not with standard ranges yet, but Eric Niebler's range-v3).

The code below:

  • groups the points vector in pairs,
  • calculates the difference between the second and the first element of each pair, and then
  • sums all those differences up.

[Demo]

auto t{ accumulate(
    points | views::sliding(2) | views::transform([](const auto& v) { return v[1] - v[0]; }),
    0.0
)};

CodePudding user response:

With C 23, use std::views::pairwise.

In the meantime, you can use iota_view. Here's a solution which will work with any bidirectional iterators (e.g. points could be a std::list):

auto distances =
    std::views::iota(points.cbegin(), std::prev(points.cend()))
  | std::views::transform([](auto const &it) { return *std::next(it) - *it; });

This can also be made to work with any forward iterators. Example:

std::forward_list<double> points{1, 2, 4, 7, 11};
auto distances =
    std::views::iota(points.cbegin())
  | std::views::take_while([end = points.cend()](auto const &it) { return std::next(it) != end; })
  | std::views::transform([](auto const &it) { return *std::next(it) - *it; })
  | std::views::common;

Note that both of these snippets have undefined behaviour if points is empty.

  • Related