3

Normal standard method of iterating is this:

for(std::vector<T>::iterator it = v.begin(); it != v.end(); ++it) {
    /* std::cout << *it; ... */
}

It involves too much typing and repetating vector variable name.

Is there a way to simplify it like in

for (auto item : vector_var) { /*...*/ }

loop but also having access to index and other functions.

I think of this:

for (auto item : myIterationClass(vector_var)) {
   item->index;
   item->value;
   item->delete();
   item->replaceWith(42);
}

It doesn't have to be fast but lean on the code I need to write.

rsk82
  • 28,217
  • 50
  • 150
  • 240

5 Answers5

4

Assuming you'd accept that your loop is slightly changed, it is certainly doable:

for (auto item : myIterationClass(vector_var)) {
    item.index();
    item.value();
    item.erase();
    item.replaceWith(42);
}

The idea is to have myIterationClass() be a thin wrapper which return iterators with a fairly custom value. The use of erase() is a bit problematic, though, as you are not supposed to change the container while it is being iterated, i.e., if these are really needed it is necessary to record the elements to be erased and process them later.

Although I don't this this is a good idea, below is a quick demo implementing index() and value(). Implementing replaceWith() would be trivial while implementing anything mutating the length of the sequence could be interesting. Given that the iterator controls the sequence it could probably be done by directly mutating the underlying sequence and adjusting the kept index appropriately. Note that there are also different approach how the iterators are represented. I randomly choose to use a combination of a pointer to the container and an index. If the sequence doesn't add or remove elements, it could also be done using two iterator and computing the index as the difference between the two.

#include <algorithm>
#include <iostream>
#include <vector>

template <typename T>
class wrapped_iterator
{
    T*                    container;
    typename T::size_type position;
public:
    wrapped_iterator(T* container, typename T::size_type position)
        : container(container)
        , position(position) {
    }
    wrapped_iterator<T>& operator*() { return *this; }
    wrapped_iterator<T>& operator++() { ++position; return *this; }
    wrapped_iterator<T>  operator++(int) {
        wrapped_iterator<T> rc(*this);
        ++*this;
        return rc;
    }
    bool operator== (wrapped_iterator<T> const& other) const {
        return position == other.position;
    }
    bool operator!= (wrapped_iterator<T> const& other) const {
        return !(*this == other);
    }
    typename T::size_type        index() const { return position; }
    typename T::const_reference& value() const { return (*container)[position]; }
};

template <typename T>
class wrapped
{
    T* container;
public:
    typedef wrapped_iterator<T> iterator;
    wrapped(T& container): container(&container) {}
    iterator begin() const { return iterator(container, 0u); }
    iterator end() const { return iterator(container, container->size()); }
};

template <typename T>
wrapped<T> wrapper(T& container) {
    return wrapped<T>(container);
}

int main()
{
    std::vector<int> v{ 7, 6, 5, 4, 3, 2, 1 };
    for (auto item : wrapper(v)) {
        std::cout << "index=" << item.index() << ' '
                  << "value=" << item.value() << '\n';
    }
}
Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • Yes, but have you any examples, any working snippet, so I can uderstand what approach take here ? – rsk82 Dec 13 '13 at 22:43
  • 1
    @rsk82: see the updated answer. I took a bit to put an example together... – Dietmar Kühl Dec 13 '13 at 23:04
  • You don't need to make the calls to `erase()` later. Take advantage of the fact that `erase()` returns an iterator to what would be the next valid item. – Mark Dec 14 '13 at 01:41
2

It's not so bad with auto alone:

for (auto it = std::begin(v), e = std::end(v); it != e; ++it)
{
    auto index = std::distance(it, e);

    // ...
}

It's maybe not pretty, but it's short enough to type and readable.


Update: Here's a mildly hacky macro "implementation", in the spirit of the range based for loop. (Beware when using with arrays.)

#include <iterator>

#define INDEX_FOR(init, idx, cont, body)  \
do                                        \
{                                         \
  auto && __x = (cont);                   \
  for (auto __it = std::begin(__x),       \
       __end = std::end(__x);             \
       __it != __end; ++__it)             \
  {                                       \
    init = *__it;                         \
    auto idx = std::distance(__it, __end);\
    body                                  \
  }                                       \
} while (false)

Example usage:

#include <iostream>
#include <vector>

int main()
{
    std::vector<int> v { 4, -9, 11, 102, 81 };

    INDEX_FOR(auto & x, i, v, {
        std::cout << "Element " << i << " = " << x << "\n";
    });
}
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • Yes, but you have here a nice one letter variable, ideal for small subroutine. Imagine you working with variables that are in the form of: numberOfProductsEligibleForDiscount etc. – rsk82 Dec 13 '13 at 22:40
  • @rsk82 - don't be afraid to introduce an alias to make your life easier; it costs nothing and in many cases can help readability (though personally, I am fond of expressive variable names, and I'd recommend limited usage). `auto& newName = oldName;` You can even scope it inside the for statement if you like. – Mark Dec 14 '13 at 01:47
0

Bjarne Stroustrup in C++11 FAQ says that for (auto item : vector_var) is valid expression. Please, see here: http://www.stroustrup.com/C++11FAQ.html#for

VladimirM
  • 817
  • 5
  • 7
  • 2
    The OP also wants the *index*... – Kerrek SB Dec 13 '13 at 22:40
  • Yes, its valid, and I wrote that in my question but then you have not access to the index, you must have another counter variable, so -> more code, more decision points, more places of potential mistake -> you land with the same problem. – rsk82 Dec 13 '13 at 22:42
  • Sorry, I thought that `index` was a method of `T` object. – VladimirM Dec 13 '13 at 22:43
0

I think this is shorter and easier to use for vectors since auto iteration constructs look somewhat creepy...

int i = 0;
while(i <= myVector.size()) {
    myVector[i];
    i++;
}

But I prefer maps/lists anyway because used correctly they have alot more performance.

Steini
  • 2,753
  • 15
  • 24
  • Yes, but what I mean is to reduce places where you can make mistake, it's easy to mess with counters, accidentally delete them, or put them in a wrong pair of braces. – rsk82 Dec 13 '13 at 22:44
  • Comparing map performance to vector performance is nonsense, as they serve completely different purposes. Saying list has better performance than vector, well it of course depends upon the application. But for most common use cases, it is just not true. (I am, of course, assuming you are referring to `namespace std` facilities here). – Benjamin Lindley Dec 13 '13 at 23:10
  • @Up: Thatswhy I said "used correctly" because that means you have to consider if a map makes sense here. However both can be be accesed by index so you can very well compare them to each other. Oh and ofcourse I talked about the std:: namespace facilities. – Steini Dec 16 '13 at 14:36
0

One way using boost is this:

for(auto item:boost::combine(vec, boost::irange(0, vec.size())))
{
    auto value = boost::get<0>(item);
    auto index = boost::get<1>(item);
    ...
}

It doesn't give you a way to erase the element, but that should probably be done using the remove-erase idiom.

Paul Fultz II
  • 17,682
  • 13
  • 62
  • 59