2
votes

If I have a std::set::iterator, how do I quickly produce an iterator which points to the next element in the set? A specific use-case for this general question follows:

Suppose I have a std::set, and I want to print out all pairs of distinct elements in the set. I believe that I can't write things like my_set.begin() + 1, because the iterator produced by set is not capable of arithmetic (unlike say that produced by a vector). So how can I achieve this?

The solution I came up with is

int main(){
    set<int> my_set {1,4,6};
    for (auto it = my_set.begin(); it != my_set.end(); it++) {
        int first_number = *it;
        for (auto it2 = it; it2!= my_set.end(); it2++) {
            if (it2 == it){it2++;} // I don't want the second number to be equal to the first
            if (it2 == my_set.end()) {break;} //If I don't put this in, it will eventually try to access my_set.end(), giving bad behavior. 
            int second_number = *it2;
            cout << "(" << first_number << ", " << second_number << ")" << endl;
        }
    }
    return 0;
}

Output:

(1, 4)
(1, 6)
(4, 6)
Program ended with exit code: 0

But this is kludgy, I think, to have to manually iterator it2, and then check it hasn't become my_set.end(). How can I do it better?

I tried making the it2 loop look like

for (auto it2 == it; it2!= my_set.end(); it2++) {...

to make it start with it2 one bigger than it, but it was not happy with this syntax.

Apologies if this question has appeared before. I wasn't able to find it.

1
Since you don't wish to print the same number as its own pair, doesn't it seem logical that the only thing that should happen inside the inner for loop is to compare both iterators, and continue if they are the same? That's the only combination of the inner and the outer loop's iterated-over value that should not be printed.Sam Varshavchik
@SamVarshavchik Yes, that makes sense.Eric Auld

1 Answers

4
votes

std::next can be used to get a new iterator that is advanced relative to an existing iterator in one call (if the second argument isn't passed, it's advanced exactly once), so your code should be possible with just:

#include <iterator>  // For std::next

int main(){
    set<int> my_set {1,4,6};
    for (auto it = my_set.begin(); it != my_set.end(); ++it) {
        int first_number = *it;
        for (auto it2 = std::next(it); it2 != my_set.end(); ++it2) {
            int second_number = *it2;
            cout << "(" << first_number << ", " << second_number << ")" << endl;
        }
    }
    return 0;
}

Try it online!

Note that I also changed your it++/it2++ expressions to ++it/++it2; for iterators, this can be important for performance, as postfix increment necessarily makes new iterator objects to return, while prefix increment can modify the iterator in place much more cheaply (returning only a reference to the iterator itself, no copies needed).