einpoklum
einpoklum

Reputation: 131515

Integer ranges in C++ - what should I do while the standard's not there yet?

I know that the C++ heavyweights are working on getting ranges into the language, or at least the standard library:

To be fair - I haven't read through the official suggestion. I'm just a humble C++ programmer who wants to use simple range functionality. What should I do today rather than after C++17 to use, say, simple integer ranges with strides? Of course my requirements may expand as I start actually using ranges, but I'm still not after the fancier stuff and all sorts of corner cases. I think.

On the more restrictive side, I am working with a somewhat old environment due to compatibility needs (CUDA), so I need something to use with GCC 4.9.3 and its corresponding standard library version, and also something I can use in GPU code (not necessarily the same library/headers, but hopefully the same).

Well, should I roll my own limited-functionality integer range type, waiting for the formalities to settle - or should I go for one of the more heavy-weight alternatives (Niebler's "Ranges v3", Boost irange, etc.)?

Upvotes: 4

Views: 357

Answers (1)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275320

Writing a simple "index" -- an input iterator that stores a type T and returns a type O=T via copy -- is easy.

What I call an index is an iterator over things that support advancement and comparison (including other iterators and integer-like things), and returns a copy of the contained object when dereferenced. It is also useful to generate ranges-over-iterators-into-ranges.

template<class T, class O=T>
struct index_t:
  std::iterator<
    std::input_iterator_tag,
    O, std::ptrdiff_t, O*, O
  >
{
  T t;
  explicit index_t(T&& tin):t(std::move(tin)){}
  explicit index_t(T const& tin):t(tin){}
  index_t()=default;
  index_t(index_t&&)=default;
  index_t(index_t const&)=default;
  index_t& operator=(index_t&&)=default;
  index_t& operator=(index_t const&)=default;

  O operator*()const{ return t; }
  O operator*(){ return t; }
  O const* operator->()const{ return &t; }
  O* operator->(){ return &t; }

  index_t& operator++(){ ++t; return *this; }
  index_t operator++(int){ auto r = *this; ++*this; return r; }

  friend bool operator==(index_t const& lhs, index_t const& rhs) {
    return lhs.t == rhs.t;
  }
  friend bool operator!=(index_t const& lhs, index_t const& rhs) { return !(lhs==rhs); }
};
template<class Scalar>
index_t<Scalar> index_to( Scalar s ) {
  return index_t<Scalar>(std::move(s));
}

or somesuch. index_t<int> is a stride-less integer iterator.

If we want stride:

template<class T, class D=std::size_t>
struct strided {
  T t;
  D stride;
  strided( T tin, D sin ):t(std::move(tin)), stride(std::move(sin)) {}

  strided& operator++(){ t+=stride; return *this; }
  strided operator++(int){ auto r = *this; ++*this; return r; }
  operator T()&&{return std::move(t);}
  operator T() const &{return t;}

  friend bool operator==(strided const& lhs, strided const& rhs){
    return lhs.t == rhs.t;
  }
  friend bool operator!=(strided const& lhs, strided const& rhs) { return !(lhs==rhs); }
};

template<class T, class D=std::size_t>
using strided_index_t = index_t<strided<T,D>, T>;

Now you just gotta write range_t<Iterator> (begin, end, empty, front, back, constructors).

template<class T>
strided_index_t<T> strided_index_to( T t, std::size_t d ) {
  return strided_index_t<T>( {std::move(t), d} );
}
template<class It>
struct range_t {
  It b,e;
  using element_type = decltype( *std::declval<It>() );
  It begin() const { return b; }
  It end() const { return e; }
  bool empty() const { return begin()==end(); }
  element_type front() const { return *begin(); }
  element_type back() const { return *std::prev(end()); }
};
template<class It>
range_t<It> range(It b, It e){
  return {std::move(b), std::move(e)};
}

now we can make our index range:

template<class Scalar>
range_t<index_t<Scalar>> index_range( Scalar b, Scalar e ) {
  return range( index_to(std::move(b)), index_to(std::move(e)) );
}
template<class Scalar>
range_t<strided_index_t<Scalar>> strided_index_range( Scalar b, Scalar e, std::size_t d ) {
  return range( strided_index_to(std::move(b), d), strided_index_to(std::move(e), d) );
}

And now we test:

for (int i : index_range(0, 10))
  std::cout << i << '\n';
for (int i : strided_index_range(0, 10, 2))
  std::cout << i << '\n';

Similar objects also exist in boost and other range-based libraries.

Live example

As an aside,

for( auto it : index_to( begin(vec), end(vec) ) )

iterates over the iterators of vec in a range-based manner. Writing

for( auto it : iterators_into(vec) )

is also easy.

Upvotes: 3

Related Questions