Your question actually has several possible answers, and most of them can be answered with Boost.Range. These really come in handy if C++20's ranges are not (yet) an option, although they are a bit more clumsy in my opinion.
Adding an index to your range
To answer your direct question Boost offers the range adaptor boost::adaptors::indexed
which you can use like so: (see it live on Coliru)
#include <iostream>
#include <vector>
#include <boost/range/adaptor/indexed.hpp>
int main()
{
std::vector const values{ 0, 1, 1 };
for (auto const indexedValue : values | boost::adaptors::indexed{ 1 })
{
std::cout << indexedValue.index() << ": " << indexedValue.value() << ", ";
}
}
Output is this:
1: 0, 2: 1, 3: 1, 4: 2, 5: 3, 6: 5, 7: 8, 8: 13, 9: 21, 10: 34,
Dropping elements from a range
As others noted handling the special case outside the loop is a good idea. To drop leading or trailing elements Boost offers boost::adaptors::sliced
. Again, I'll demonstrate it with an example: (see it live on Coliru)
#include <iostream>
#include <vector>
#include <boost/range/adaptor/sliced.hpp>
int main()
{
std::vector const values{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
for (auto const value : values | boost::adaptors::sliced{ 2, values.size() - 1 })
{
std::cout << value << ", ";
}
}
Here, I dropped the first two and the last elements, the output is this:
2, 3, 4, 5, 6, 7, 8,
Combining range adaptors
The pipe syntax already suggests it, a range adaptor yields a range, so you can adapt it again: (see it live on Coliru)
#include <iostream>
#include <vector>
#include <boost/range/adaptor/indexed.hpp>
#include <boost/range/adaptor/sliced.hpp>
int main()
{
std::vector const values{ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 };
for (auto const indexedValue : values
| boost::adaptors::indexed{ 1 }
| boost::adaptors::sliced{ 2, values.size() - 1 })
{
std::cout << indexedValue.index() << ": " << indexedValue.value() << ", ";
}
}
yields the output
3: 1, 4: 2, 5: 3, 6: 5, 7: 8, 8: 13, 9: 21,
Boost provides several other adaptors like reversed
, indirected
, transformed
or fitlered
to name just the really useful ones. They are worth a read.
Accessing the iterator
Lastly, your question starts with "Is there a way to access the iterator", and it turns out, there is. All we need to do™ is write our own range adaptor for this. This seems not really useful but it has helped me to refactor a very nasty raw loop from using iterator in the for statement to a range-based for loop. This made the code easier, you can immediately see that the loop actually visits each element in the range once but inspects the neighbors of the current element.
A simple solution can be achieved in 4 easy steps:
- Write a class template that wraps an iterator with a member function
value()
to dereference the wrapped iterator and a member function iterator()
to retrieve a copy of the wrapped iterator
- Write an iterator template which contains the class from the previous step and yields a reference to it when dereferenced.
- Define a range type with
boost::iterator_range
using the iterator of the previous step.
- Write an
operator|()
factory and a tag class which selects the factory to convert a regular range into the adapted range type defined in the previous step.
The following solution works but may need some tweaking for edge cases. Also, it uses return type deduction of C++14 for the factory for simplicity, in C++11 a bit more work is needed. (see it live on Coliru)
#include <iostream>
#include <vector>
#include <boost/range/iterator_range.hpp>
template <typename Iterator>
class ValueAndIterator
{
public:
using iterator_type = Iterator;
using value_type = typename std::iterator_traits<iterator_type>::value_type;
using reference = typename std::iterator_traits<iterator_type>::reference;
public:
explicit ValueAndIterator(Iterator const iter)
: m_iter{ iter }
{}
[[nodiscard]] reference value() const
{
return *m_iter;
}
[[nodiscard]] iterator_type iterator() const
{
return m_iter;
}
private:
Iterator m_iter{};
};
template <typename Iterator>
class IteratoredIterator
{
public:
using base_iterator = Iterator;
using difference_type = typename std::iterator_traits<base_iterator>::difference_type;
using value_type = ValueAndIterator<base_iterator> const;
using pointer = value_type *;
using reference = value_type &;
using iterator_category = std::forward_iterator_tag; // This is sufficient for the range-based for loop, this could be improved with some effort
public:
IteratoredIterator() = default;
explicit IteratoredIterator(base_iterator const iter)
: m_valueIter{ iter }
{}
IteratoredIterator & operator++()
{
m_valueIter = ValueAndIterator<Iterator>{ ++m_valueIter.iterator() };
return *this;
}
IteratoredIterator operator++(int)
{
IteratoredIterator result{ *this };
++*this;
return result;
}
[[nodiscard]] reference operator*() const
{
return m_valueIter;
}
[[nodiscard]] pointer operator->() const
{
return &m_valueIter;
}
[[nodiscard]] base_iterator base() const
{
return m_valueIter.iterator();
}
[[nodiscard]] bool operator==(IteratoredIterator const other) const
{
return m_valueIter.iterator() == other.m_valueIter.iterator();
}
[[nodiscard]] bool operator!=(IteratoredIterator const other) const
{
return !operator==(other);
}
private:
ValueAndIterator<Iterator> m_valueIter;
};
constexpr class IteratoredTag {} Iteratored;
template <typename Iterator>
using IteratoredRange = boost::iterator_range<IteratoredIterator<Iterator>>;
template <typename Range>
[[nodiscard]] auto operator|(Range const & range, IteratoredTag)
{
using iterator = decltype(std::begin(range));
return IteratoredRange<iterator>{ IteratoredIterator<iterator>{ std::begin(range) },
IteratoredIterator<iterator>{ std::end(range) } };
}
template <typename Range>
[[nodiscard]] auto operator|(Range & range, IteratoredTag)
{
using iterator = decltype(std::begin(range));
return IteratoredRange<iterator>{ IteratoredIterator<iterator>{ std::begin(range) },
IteratoredIterator<iterator>{ std::end(range) } };
}
int main()
{
std::vector values{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
for (auto const & value : values | Iteratored)
{
*value.iterator() *= 2;
std::cout << value.value() << ", ";
}
}
It uses the iterator to double each entry in values
before printing the value. Consequently, the output it as follows:
0, 2, 4, 6, 8, 10, 12, 14, 16, 18,
for
loop (if that solves your problem easily). Don't force yourself doing complex things which need not to be complex. – Anthologyfor
loop if none of the algorithms fit. – Elegantif (elem == container.front()) continue;
as a workaround? Not overly complex, is it? And we can still use the concise syntax. Still better than 3 lines of manual iterator handling as in pre-C++11 IMHO.. – Ashlynashmanptr_vector
. Also if there's one thing you should learn with C++ is this: don't be lazy. I know iterators are painful, but they are the correct way to do this and will help you learn how to write robust generic code. Indices don't always make sense but iterators always do. – Stoecker