In this section, we will recreate the zip function as known from Haskell or Python. It will be hardcoded to vectors of double variables in order to not distract from iterator mechanics:
- First, we need to include some headers:
#include <iostream>
#include <vector>
#include <numeric>
- Next, we define the zip_iterator class. While iterating over a zip_iterator range, we will get a pair of values from the two containers at every iteration step. This means that we iterate over two containers at the same time:
class zip_iterator {
- The zip iterator needs to save two iterators, one for each container:
using it_type = std::vector<double>::iterator;
it_type it1;
it_type it2;
- The constructor simply saves the iterators from the two containers that we would like to iterate over:
public:
zip_iterator(it_type iterator1, it_type iterator2)
: it1{iterator1}, it2{iterator2}
{}
- Incrementing the zip iterator means incrementing both the member iterators:
zip_iterator& operator++() {
++it1;
++it2;
return *this;
}
- Two zip iterators are unequal if both the member iterators are unequal to their counterparts in the other zip iterator. Usually, one would use logical or (||) instead of and (&&), but imagine that the ranges are not of equal length. In such a case, it would not be possible to match both the end iterators at the same time. This way, we can abort the loop when we reach the first end iterator of either range:
bool operator!=(const zip_iterator& o) const {
return it1 != o.it1 && it2 != o.it2;
}
- The equality comparison operator is just implemented using the other operator, but negating the result:
bool operator==(const zip_iterator& o) const {
return !operator!=(o);
}
- Dereferencing the zip iterator gives access to the elements of both the containers at the same position:
std::pair<double, double> operator*() const {
return {*it1, *it2};
}
};
- This was the iterator code. We need to make the iterator compatible with STL algorithms, so we define the needed type trait boilerplate code for that. It basically says that this iterator is just a forward iterator, and it returns pairs of double values when dereferenced. Although we do not use difference_type in this recipe, different implementations of the STL might need it in order to compile:
namespace std {
template <>
struct iterator_traits<zip_iterator> {
using iterator_category = std::forward_iterator_tag;
using value_type = std::pair<double, double>;
using difference_type = long int;
};
}
- The next step is to define a range class that returns us zip iterators from its begin and end functions:
class zipper {
using vec_type = std::vector<double>;
vec_type &vec1;
vec_type &vec2;
- It needs to reference two existing containers in order to form zip iterators from them:
public:
zipper(vec_type &va, vec_type &vb)
: vec1{va}, vec2{vb}
{}
- The begin and end functions just feed pairs of begin and end pointers in order to construct zip iterator instances from that:
zip_iterator begin() const {
return {std::begin(vec1), std::begin(vec2)};
}
zip_iterator end() const {
return {std::end(vec1), std::end(vec2)};
}
};
- Just as in the Haskell and Python examples, we define two vectors of double values. We also define that we use namespace std within the main function by default:
int main()
{
using namespace std;
vector<double> a {1.0, 2.0, 3.0};
vector<double> b {4.0, 5.0, 6.0};
- The zipper object combines them to one vector-like range where we see pairs of a and b values:
zipper zipped {a, b};
- We will use std::accumulate in order to sum all the items of the range together. We can't do it directly because that would mean that we sum up the instances of std::pair<double, double> for which the concept of sum is not defined. Therefore, we will define a helper lambda that takes a pair, multiplies its members, and adds it to an accumulator. The std::accumulate works well with lambdas with such a signature:
const auto add_product ([](double sum, const auto &p) {
return sum + p.first * p.second;
});
- Now, we feed it to std::accumulate, together with the begin and end iterator pair of the zipped ranges and a start value of 0.0 for the accumulator variable, which, in the end, contains the sum of the products:
const auto dot_product (accumulate(
begin(zipped), end(zipped), 0.0, add_product));
- Let's print the dot product result:
cout << dot_product << 'n';
}
- Compiling and running the program yields the correct result:
32