So I was recently thinking about the code of an iterating for loop and why there is no functionality to retrieve an iterator with a range-based for loop. The range-based for loop was a hugely popular addition to C++, however it is limited to only returning dereferenced iterators. I don’t think I need to convince anyone of why one would need an iterator rather than a dereferenced iterator, there are plenty of use cases to warrant such a loop. The issue here is that to iterate over a range using a classic for loop takes quite a lot of characters, even with the wonderful auto it that C++11 provides.

The Problem

Range-based for loops work like this:

//get a copy of each value in the container
for(auto value : container) { }

//get a reference of each value in the container
for(auto & v : container) { }

But sometimes you need this:

//get an iterator for each value in the container
for(auto it = container.begin(); it != container.end(); ++it) { }

Ideally, it would work like this:

//ideal syntax for getting an iterator for each value in the container
for_it(auto it : container) { }

I haven’t mastered meta-programming, so adding a for_it definition is still on the horizon. So I propose this:

//syntax I have settled for to get an iterator for each value in the container
for(auto it : iit(container)) { }

The Code

The full solution can be found here

The Iterator

The code here is small but powerful. itr (the iterator iterator) is simply a container that holds a standard forward iterator. All operations work the same as most other forward iterators except that the iterator iterator dereference operator returns an iterator, not a dereferenced iterator.

template <typename T>
class itr : public T
{
  T _obj;
 public:
    itr(T&& obj) : _obj(std::move(obj)) {}
    bool operator!=(const itr& other) const { return _obj != other._obj; }
    T operator*() const { return _obj; }
    const itr& operator++() { ++_obj; return *this; }
};

This is a minimal implementation of only what is needed for range-based for loops. A proper implementation would implement std::iterator and also follow the rules of `InputIterator’

In addition to the iterator class, a make function has been implemented. The idea here is to save the user from having to define the type of itr themselves, a similar idea to make_tuple and make_shared. My make function has been simply defined as iit to save some characters.

template <typename T>
auto iit(T&& t) -> typename std::conditional<std::is_lvalue_reference<decltype(t)>::value,
                                 ref<T>,
                                 val<T>>::type
{
    return std::forward<T>(t);
}

Ref and Val

iit is defined around a universal reference (l-value or r-value) and a return type conditionally tied to if the type is l-value or r-value. This allows iit to accept the value by reference using the class ref, or move construct the type val. Primarily, the ref and val encapsulating containers provide the begin and end functions which return itr. Additionally, having a ref and val class allows iit to encapsulate the passed in container and keep an r-value alive through the lifetime of ref. Here are the definitions of ref and val:

template <typename T>
class val
{
  T _obj;
 public:
 val(T&& obj) : _obj(std::move(obj)) {}

  auto begin() const -> itr<decltype(std::begin(this->_obj))> { return std::begin(_obj); }
  auto end() const -> itr<decltype(std::end(this->_obj))> { return std::end(_obj); }
};

template <typename T>
class ref
{
  T& _obj;
public:
 ref(T& obj) : _obj(obj) {}
  
  auto begin() const -> itr<decltype(std::begin(this->_obj))> { return std::begin(_obj); }
  auto end() const -> itr<decltype(std::end(this->_obj))> { return std::end(_obj); }
};

Usage

for(auto it : iit(vector<int>{1,2,3,4})) 
  std::cout << *it << std::endl;

Without the val container to capture the r-value and store it, the vector would go out of scope.

vector<int> v = {1,2,3,4};
for(auto it : iit(v)) 
  if(it == v.begin())
    std::cout << *it << std::endl;

Here, v is sent to ref which only holds a reference to vector, no copies needed.

Thanks for reading!

Brian Rackle