Multiple iterators to a complex range

c++ ranges
range-v3 examples
stl range c
fluent c++
ranges ts
move elements in vector c
transform if c++
c vector view

I am trying to have multiple iterators to a bit more complex range (using range-v3 library) -- manually implementing a cartesian product, using filter, for_each and yield. However, when I tried to hold multiple iterators to such range, they share a common value. For example:

#include <vector>
#include <iostream>
#include <range/v3/view/for_each.hpp>
#include <range/v3/view/filter.hpp>

int main() {
    std::vector<int> data1{1,5,2,7,6};
    std::vector<int> data2{1,5,2,7,6};
    auto range =
            data1
            | ranges::v3::view::filter([](int v) { return v%2; })
            | ranges::v3::view::for_each([&data2](int v) {
                return data2 | ranges::v3::view::for_each([v](int v2) {
                    return ranges::v3::yield(std::make_pair(v,v2));
                });
            });
    auto it1 = range.begin();
    for (auto it2 = range.begin(); it2 != range.end(); ++it2) {
        std::cout << "[" << it1->first << "," << it1->second << "] [" << it2->first << "," << it2->second << "]\n";
    }
    return 0;
}

I expected the iterator it1 to keep pointing at the beginning of the range, while the iterator it2 goes through the whole sequence. To my surprise, it1 is incremented as well! I get the following output:

[1,1] [1,1]
[1,5] [1,5]
[1,2] [1,2]
[1,7] [1,7]
[1,6] [1,6]
[5,1] [5,1]
[5,5] [5,5]
[5,2] [5,2]
[5,7] [5,7]
[5,6] [5,6]
[7,1] [7,1]
[7,5] [7,5]
[7,2] [7,2]
[7,7] [7,7]
[7,6] [7,6]
  • Why is that?
  • How can I avoid this?
  • How can I keep multiple, independent iterators pointing in various locations of the range?
  • Should I implement a cartesian product in a different way? (that's my previous question)

While it is not reflected in the MCVE above, consider a use case where someone tries to implement something similar to std::max_element - trying to return an iterator to the highest-valued pair in the cross product. While looking for the highest value you need to store an iterator to the current best candidate. It cannot alter while you search, and it would be cumbersome to manage the iterators if you need a copy of the range (as suggested in one of the answers).

Materialising the whole cross product is not an option either, as it requires a lot of memory. After all, the whole point of using ranges with filters and other on-the-fly transformations is to avoid such materialisation.

Range for loop with multiple containers, What is the preferred C++11 way to specify a range-for loop over both (or all) containers simultaneously? Does it involve choosing one container/iterator to write  Iterators should not attempt to implement any custom logic based on the Range(s) provided to seek and Iterators should not return any Keys that fall outside of the provided Range. The second argument, a Collection<ByteSequence> , is the set of column families which should be retained or excluded by this Iterator.

It seems that the resulting view stores state such that it turns out to be single pass. You can work around that by simply making as many copies of the view as you need:

int main() {
    std::vector<int> data1{1,5,2,7,6};
    std::vector<int> data2{1,5,2,7,6};
    auto range =
            data1
            | ranges::v3::view::filter([](int v) { return v%2; })
            | ranges::v3::view::for_each([&data2](int v) {
                return data2 | ranges::v3::view::for_each([v](int v2) {
                    return ranges::v3::yield(std::make_pair(v,v2));
                });
            });

    auto range1= range;         // Copy the view adaptor
    auto it1 = range1.begin();

    for (auto it2 = range.begin(); it2 != range.end(); ++it2) {
        std::cout << "[" << it1->first << "," << it1->second << "] [" << it2->first << "," << it2->second << "]\n";
    }

    std::cout << '\n';
    for (; it1 != range1.end(); ++it1) { // Consume the copied view
        std::cout << "[" << it1->first << "," << it1->second << "]\n";
    }
    return 0;
}

Another option would be materializing the view into a container as mentioned in the comments.


Keeping in mind the aforementioned limitation of single-pass views, it is not really hard to implement a max_element function that returns an iterator, with the important drawback of having to compute the sequence one time and a half.

Here's a possible implementation:

template <typename InputRange,typename BinaryPred = std::greater<>>
auto my_max_element(InputRange &range1,BinaryPred &&pred = {}) -> decltype(range1.begin()) {
    auto range2 = range1;
    auto it1 = range1.begin();
    std::ptrdiff_t pos = 0L;

    for (auto it2 = range2.begin(); it2 != range2.end(); ++it2) {
        if (pred(*it2,*it1)) {
            ranges::advance(it1,pos);   // Computing again the sequence as the iterator advances!
            pos = 0L;
            }
        ++pos;
        }
    return it1; 
}

Multi-Range For Loop, This would then also map the dereferenced tuple iterators into the defined names​. Calling get is ugly but that's the “multiple return value” problem which A std::​for_each(Ranges, Function) is possible but complex to  Python Iterators. An iterator is an object that contains a countable number of values. An iterator is an object that can be iterated upon, meaning that you can traverse through all the values. Technically, in Python, an iterator is an object which implements the iterator protocol, which consist of the methods __iter__() and __next__().

An iterator is a pointer to an element in the vector, in this case, it1 points to the beginning of the vector. And hence, if you are trying to point the iterator to the same location of the vector, they will be the same. However, you can have multiple iterators pointing to different locations of the vector. Hope this answers your question.

Learning Python: Powerful Object-Oriented Programming, [x ** 2 for x in range(1, 6)] [1, 4, 9, 16, 25] However, classes may be better at modeling more complex iterations, especially when they can Multiple. Iterators. on. One. Object. Earlier, I mentioned that the iterator object may be defined as a  Introduction to Iterators in C++ An iterator is an object (like a pointer) that points to an element inside the container. We can use iterators to move through the contents of the container.

Ada-Europe '93: 12th Ada-Europe International Conference, "Ada , The structure need not be complex. For example, two basic types of iterators may be defined for arrays, step iterators and bisection iterators. type Array_Range is (<>) ; type Array_Type is array (Array_Range range <>) of Object_Type; type  I love this question because range objects in Python 3 (xrange in Python 2) are lazy, but range objects are not iterators and this is something I see folks mix up frequently. In the last year I’ve heard Python beginners, long-time Python programmers, and even other Python trainers mistakenly refer to Python 3’s range objects as iterators.

Hands-On Blockchain with Hyperledger: Building decentralized , In more complex scenarios, keys can be constructed from multiple attributes. The range functions return an iterator (StateQueryIteratorInterface) over a set of  By using the appropriate method, you can easily refer to multiple ranges. Use the Range and Union methods to refer to any group of ranges. Use the Areas property to refer to the group of ranges selected on a worksheet. Using the Range Property. You can refer to multiple ranges with the Range property by inserting commas between two or more references. The following example clears the contents of three ranges on Sheet1.

Ranges: the STL to the Next Level, This post shows what ranges are, how they are designed to be used, and where because instead of having to mentally parse a complex for loop, a reader to take directly ranges as parameters, instead of two iterators, like:. C++20 introduces a new system of iterators based on concepts that are different from C++17 iterators. While the basic taxonomy remains similar, the requirements for individual iterator categories are somewhat different.

Comments
  • That range is a proxy object primarily thought to be lazily evaluated as it is traversed. If you want to traverse the range multiple times you probably better first transfer it to a container.
  • @metalfox I don't mind reevaluating stuff multiple times if necessary, but materializing a cross product into a container - that will take needlessly a lot of memory.
  • I deeply appreciate your detailed answer. This opens my eyes onto some aspects of iterators I was not aware of. I can see now how the bullet you cited in the multipass guarantee is problematic. However, if we would replace it with something slightly weaker, the cross product could be implemented as a pair of ForwardIterator-s and referencing it would return an object holding two references to were each component references. I think it would work with std::max_element and other functions. The limiting factor seems to be the formalisation and not the general algorithmic inability to do so.
  • Uch, it will work for my minimum working example showing the initial problem, but imagine you want to implement a maximum function over the range, returning it2 to the found maximum, while iterating with it1. Under a certain condition (found higher value) it2 needs to set to the current value of it1. Setting it to "the same element as it1 but within the range copy" would be cumbersome to achieve I think? I could, for example, remember how far I need to advance. But at that point I am asking myself, why use this kind of ranges if they are so weird to use?!
  • @CygnusX1 Note that you are combining every possible combination the elements of data1 and data2 on the fly (lazily). That’s very powerful. decltype(range)::iterator becoming an InputIterator seems to me a reasonable price to pay. Some views downgrade the iterator_category of the input range. For example view::filter can be at most BidirectionalRange even when the input range provides random access. In your example you are creating temporary pairs (v,v2). It is quite logical to me that range is single pass.
  • @CygnusX1 Do you really need to return an iterator? Can’t you just return the value it wraps?
  • std::max_element for regular container returns an iterator. I am trying to implement something similar here. Cross product seems to me as a trivial operation, and I don't really see a reason for it to be of any lesser category than the types of iterators it combines. But I am a novice range-v3 user, the above behavior - one iterator per range - seems totally counter-intuitive and unexpected. Took me a while to debug it and figure out the library tricked me leading to the question above.
  • @CygnusX1 I agree that caching begin may be counter-intuitive. I have added some rationale I found to the answer.
  • But I increment it2 while I do not increment it1. Yet, it1 still points at the same location as it2 after multiple iterations.