Acts like an iterator, but counts

I’m making a “counting iterator” which emulates a constant container of consecutive values of some integral type.

It is mostly boilerplate, going through the list of requirements for different iterator types and making sure all the functions are there to be a bidirectional random-access iterator. That is why all the functions have explicit return types declared even though they are trivial — to make sure it matches what the docs state.

Everything is noexcept because they are only simple integer operations. If someone instantiates it with a bignum class, that will not necessarily be right, but I don’t expect that at a use case. I’ll just document it as not supporting exceptions in the simple arithmetic primitives, and IAC never tested for weird classes.

This library is supposed to be simple and fairly minimal for a set of common use cases. It is meant to help people understand the magic before understanding the fullness of Range.v3, and show how things are done without a lot of metaprogramming or huge infrastructure behind the class.

An intro to this is on Code Project: DIY µ-range, and the complete code is in GuiHub.

count_iter.h

#include <type_traits>
#include <iterator>

namespace Dlugosz::d3 {

inline namespace minirange {

template <typename T = int>
struct count_iter {
T value;
using difference_type = std::make_signed_t<T>;
explicit count_iter (T value) noexcept : value{value}
{
static_assert (std::is_integral_v<T>, "count_iter is meant for integer-like types");
}
T operator* () const noexcept { return value; }
T& operator* () noexcept { return value; }
// no operator-> because T has no members!
count_iter& operator++ () noexcept { ++value;  return *this; }
count_iter operator++ (int) noexcept { auto temp= *this;  ++value;  return temp;  }
count_iter& operator+= (difference_type n) noexcept {  value+=n;  return *this;  }
count_iter& operator-- () noexcept { --value;  return *this; }
count_iter operator-- (int)  { auto temp= *this;  --value;  return temp;  }
count_iter& operator-= (difference_type n) {  value-=n;  return *this;  }
};

template <typename T>
bool operator== (const count_iter<T>& left, const count_iter<T>& right) noexcept
{
return left.value == right.value;
}

template <typename T>
bool operator!= (const count_iter<T>& left, const count_iter<T>& right) noexcept
{
return !(left==right);
}

template <typename T>
count_iter<T> operator+ (count_iter<T> left, typename count_iter<T>::difference_type right) noexcept
{
left += right;
return left;
}

template <typename T>
count_iter<T> operator+ (typename count_iter<T>::difference_type left, count_iter<T> right) noexcept
{
right += left;
return right;
}

template <typename T>
count_iter<T> operator- (count_iter<T> left, typename count_iter<T>::difference_type right) noexcept
{
left -= right;
return left;
}

template <typename T>
typename count_iter<T>::difference_type
operator- (count_iter<T> left, count_iter<T> right) noexcept
{
return right.value - left.value;
}

template <typename T>
bool operator< (count_iter<T> left, count_iter<T> right) noexcept
{
return left.value < right.value;
}

template <typename T>
bool operator> (count_iter<T> left, count_iter<T> right) noexcept
{
return left.value > right.value;
}

template <typename T>
bool operator<= (count_iter<T> left, count_iter<T> right) noexcept
{
return left.value <= right.value;
}

template <typename T>
bool operator>= (count_iter<T> left, count_iter<T> right) noexcept
{
return left.value >= right.value;
}

}}

template <typename T>
struct std::iterator_traits<Dlugosz::d3::count_iter<T>> {
using difference_type = typename Dlugosz::d3::count_iter<T>::difference_type;
using value_type = T;
using pointer = T;
using reference = T&;
using iterator_category = std::bidirectional_iterator_tag;
};


some usage code

void f() {

count_iter B {1};
count_iter E {42};

for (auto it=B;  it != E;  ++it)
cout << ' ' << *it;
cout << '\n';
}

void g() {

range_view<count_iter<>> counter {count_iter{1},count_iter{42}};

for (auto i : counter)
cout << ' ' << i;
cout << '\n';

int total = std::accumulate (counter.begin(), counter.end(), 0);
cout << "total accumulated is: " << total << '\n';

auto found = std::find (counter.begin(), counter.end(), 18);
cout << "found 18? " << *found << '\n';

bool found2 = std::binary_search (counter.begin(), counter.end(), 18);
cout << "binary search? " << found2 << '\n';
}


I would suggest putting the static assert in the class itself, preferably first thing right after the opening brace, rather than burying it in the constructor. That gives the false impression that the static assertion isn't done until an object is constructed. Seems to me the requirements on T should be stated right up front in a template, before getting on with any of the template's actual business. The static assert string is also a bit superfluous unless you're targeting C++11 or C++14, but this is tagged C++17.

I also wonder why you implement +=, +, -, <, >, and so on, if this is just supposed to be a bidi-iterator. If you want code simplicity, it seems self-defeating to include unnecessary stuff - especially when it ends up taking 2⁄3 of the code. Or is it actually supposed to be random-access?

• Good catch: It was supposed to be random-access. Thanks for spotting that! Jun 2, 2018 at 20:26

You can correctly declare noexcept only when it's true:

count_iter& operator++ () noexcept(noexcept(++value))
{ ++value;  return *this; }


It's a shame this isn't C++20, as Concepts and <=> would save quite a bit of verbosity.

I couldn't see where range_view (in the example g()) comes from (Ranges V3 library? I'm also guessing there's a using std::cout; somewhere, too), but it would be useful to provide a couple of factory methods for such ranges if you'd like usage to be as convenient as Python range objects.

• I updated the description in the post. range_view is detailed here. Nice factories for such views will come later; this is only testing one thing, so making it a range (to use with the for loop) is as minimal as possible. Jun 2, 2018 at 20:23