C++ Iterators what am I doing wrong

Dear Members,

I have some issues with the C++ iterators. Coming from some other languages, I am used to syntax like:

1
2
let numbers = vec![2, 3, 4, 5, 6];
let sum_doubles: i32 = numbers.map(|n| 2 * n).sum();


I'm kind of struggling to get something like this working in C++. With a lot of pain I was able to implement an iterator, iter_map (see https://github.com/xfbs/euler/blob/master/lib/cpp/include/euler/iterators.hpp) that takes an existing iterator, and applies a function to each element, such that the code would look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// input numbers
std::vector<int> numbers = {2, 3, 4, 5, 6};
auto numbers = std::begin(numbers);
auto numbers_end = std::end(numbers);

// double each number iterator
auto double_num = [](const uint64_t &num) {
  return 2 * num;
};
auto numbers_doubled = map_iter(numbers, double_num);
auto numbers_doubled_end = map_iter(numbers_end, double_num);

// compute sum
auto sum_doubles = std::accumulate(numbers_doubled, numbers_doubled_end, 0);


However, as you can easily see, even this is WAY more verbose than the rust/ruby/crystal/scheme or in general the functional approach to code like this.
Is there a way, with the STL, to rewrite this code in a cleaner, more concise way? Or is there a way to write a custom iterator class that has easy and reusable methods like .map(), .fold(), .sum(), etc?

Cheers & thanks in advance,
xfbs
Last edited on
C++ has it's own way of doing things, so trying to do it in the same way like in other languages is probably not the best way.

1
2
3
4
5
6
7
8
std::vector<int> numbers = { 2, 3, 4, 5, 6 };

// double numbers in vector
for (int& num: numbers)
  num *= 2;

// get the sum
double sum = std::accumulate(numbers.begin(), numbers.end(), 0.0);
You can avoid a raw for loop with std::transform.
std::transform(numbers.begin(), numbers.end(), numbers.begin(), [](int i) -> int { return i * 2; });
Now which is more legible is a different subjective matter...
Last edited on
The standard library's generalized map/reduce is called std::transform_reduce, or in earlier versions, std::inner_product.

1
2
3
4
5
6
int main() {
  std::vector numbers {2, 3, 4, 5, 6};
  std::cout << std::transform_reduce(std::begin(numbers), std::end(numbers), 0,
                                     std::plus<>{}, [](auto i){ return i*2; })
            << '\n';
}


This particular case isn't too bad, but in general it can be worse: there aren't iterator adapters in the standard library -- you'll have to turn elsewhere for those. For example, there's boost::transform_iterator already:
http://www.boost.org/doc/libs/1_66_0/libs/iterator/doc/transform_iterator.html

As is usual, C++ can do better, if you're willing to accept sufficient black magic.
1
2
3
4
5
int main() {
  std::vector numbers{2, 3, 4, 5, 6};
  std::cout << ranges::accumulate(
      numbers | ranges::view::transform([](auto i) { return i * 2; }), 0);
}

It turns out it's possible to supply lazy generic operations over a range of any type. If you're interested in how this works, read the below, or consider asking another question.

See:
https://ericniebler.github.io/range-v3/index.html

Also of interest:
http://www.boost.org/doc/libs/1_65_1/libs/iterator/doc/index.html
http://www.boost.org/doc/libs/1_66_0/libs/range/doc/html/index.html


Last edited on
@Thomas1965: I totally get your point.. I mean I have to work with the language, not fight it! :p

I think that all of your solutions are pretty good and more concise than mine (it's obvious that I haven't done c++ for 6 years...), and I guess I should have asked my question a little different but, the problem with these answers is that they don't scale well, imagine if I'm working with a set of 2 million integers, and I need to filter that or run functions on it (mapping the data) multiple times, I'll have a lot of overhead from copying stuff around.

I'm definitely going to scrap my attempts at implementing this since that wasn't a smart idea to start with (more like an experiment). I'm also going to try to stay away from boost for now, because it's one huge library to pull in for such a small feature. But I did have a look at range-v3 and it seems like a pretty good match for what I need!

Are there any downsides to using range-v3?
Why don't you look at valarray?

If v is a valarray<int>, then 2*v will double every element of v, whilst v.sum() will do what it says on the can.
Are there any downsides to using range-v3

Primarily, it's not stable. (It claims to be "experimental" on the tin.)
xfbs wrote:
..... the problem with these answers is that they don't scale well, imagine if I'm working with a set of 2 million integers, and I need to filter that or run functions on it (mapping the data) multiple times, I'll have a lot of overhead from copying stuff around.


You might be surprised at how efficient std::vector is. C++ has move semantics, references, copy elision, there are cache effects too.

IMO the answers presented so far do scale very well.

Regards

Edit: you might get more mileage if you present your actual problem, rather than an abstract version of it :+)
Last edited on
One big advantage of these iterator adaptors is that it reduces the number of "steps" required for a computation.

For example, A solution like OP's call to std::accumulate with map_iterator, or transform_reduce as above combines the steps of doubling each element and summing them so that only one pass over the elements is required. This extends nicely to other kinds of sequences (even infinite sequences) and for any number of steps.

The Wikipedia page on expression templates explains the idea quite nicely:
https://en.wikipedia.org/wiki/Expression_templates
Last edited on
Topic archived. No new replies allowed.