7

There is a zip_with function provided by Eric Niebler.

But, now that C++20 have support for ranges I would like to build something similar.

The problem with filter and transform is that they iterate a range?

How would I go about doing this? I have been stuck with this for a while and would hate to use Expression Templates for the same.

Let's say for example I have two vectors M1{1,2,3} and M2{4,5,6}.

I would like to use the ranges library to overload a operator to return a view which contains matrix addition of these two - M1+M2 := {5,7,9}.

With ranges-v3, I can perform auto sum = zip_with(std::plus,M1,M2);

The above expression is evaluated lazily. How can I re-create this expression with C++20 Ranges?

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
Aniket Chowdhury
  • 332
  • 3
  • 13

2 Answers2

2

The principle is quite trivial. Create an iterator that stores an iterator for each vectors, that when incremented, increments the two stored iterators and does the addition only when it is dereferenced.

Here is a piece of code that shoes the principle:

template <class It1, class It2>
struct adder_iterator{
  It1 it1;
  It2 it2;

  decltype(auto)
  operator++(){
    ++it1; ++it2;
    return *this;
    }

  auto
  operator *()const{
    return *it1+*it2;
    }
  //....
  };

You will also need to implement a sentinel and a view (by deriving from std::view_interface).

The sentinel is the end iterator. You can use the adder_iterator class for that. But you can think about optimization: in your view constructor, you ensure that the shortest vector begin iterator is always it1 end then only use this iterator to test the end of the iteration. You should try to see.

Oliv
  • 17,610
  • 1
  • 29
  • 72
  • That's what I was looking for. I understand the view part, can you tell me a bit more about the sentinel. And, do I need to implement an adaptor too? – Aniket Chowdhury Mar 19 '20 at 19:20
  • @AniketChowdhury The adaptor object is only usefull to get the stream like syntax "a_view | an_adaptor | an_other_adaptor" ... – Oliv Mar 19 '20 at 21:54
  • 1
    @AniketChowdhury The sentinel is just a 'end' iterator that has a type that differs than the begin iterator. It is mostly used because it makes range like algorithm more efficient. For example, in this case, in a dumb implementation of the zip_range, the test to check if the adder_iterator has reached the end could be to test if any of it1 or it2 as reached the end. But that would be overkilling if both iterators were random_access_iterators. – Oliv Mar 19 '20 at 21:57
  • 1
    @AniketChowdhury The first time I read about sentinel were in Niebler blog: http://ericniebler.com/ – Oliv Mar 19 '20 at 22:05
  • Another thing, where does the view_interface derived class get called. If I understand it, we are calling `adder_iterator` from `adder_view` constructor. Is that correct? It seems a bit confusing still. – Aniket Chowdhury Mar 20 '20 at 12:21
  • @AniketChowdhury `adder_view`, could derive from `view_interface`. It use CRTP trick. It is just an helper class to avoid to implement all member function a view may have. http://eel.is/c++draft/ranges#view.interface-1 – Oliv Mar 20 '20 at 14:04
1

I don't know what is allowed in c++20, but the following works with range-v3's cpp20 namespace.

#include <range/v3/all.hpp>
#include <vector>
#include <iostream>

int main() {

  std::vector<int> m1 = {1, 2, 3};
  std::vector<int> m2 = {4, 5, 6};

  auto sum = ranges::cpp20::views::transform(m1, m2, std::plus{});

  for (auto i : sum)
      std::cout << i << " "; // 5 7 9
}
cigien
  • 57,834
  • 11
  • 73
  • 112