4

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.)?

zx8754
  • 52,746
  • 12
  • 114
  • 209
einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • That sort of depends on what you want to be able to do. If you just need a range boost.range might suffice; if you want the full functionality you can just use the range library eric niebler wrote; it's on github. – Klemens Morgenstern Mar 11 '16 at 10:42
  • @KlemensMorgenstern: Boost and CUDA don't live together too well, I would need to "extract" irange/range from Boost. It may or may not be difficult to do. – einpoklum Mar 11 '16 at 10:47
  • 1
    It's a header-only library, so go for it. I don't know CUDA but boost.range is compliant C++ Code. – Klemens Morgenstern Mar 11 '16 at 12:07
  • What do you mean by integer range: you mean the range `[1,2,3,4,5,6)` and `[1,3,5,7)`? (ie, a range of integers with a fixed stride between them). Or something else? – Yakk - Adam Nevraumont Mar 11 '16 at 19:01
  • @Yakk: I mean `for(auto i = i_start; i < i_end; i += i_stride) { ... }` – einpoklum Mar 11 '16 at 19:31
  • Boost has many ranges in the MPL (eg. `iterator_range` and `range_c` which can specify the underlying type as well, such as an `int`). Those may be useful to you for this. – callyalater Sep 14 '16 at 20:00

1 Answers1

3

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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Isn't `std::iterator` going to be removed from the standard? – einpoklum Mar 11 '16 at 21:19
  • @einpoklum Why do you think that? It is merely a helper type that makes defining valid iterators a touch easier... – Yakk - Adam Nevraumont Mar 11 '16 at 21:19
  • I think I read about it in some kind of C++17 status update. Never mind. Another point: Since D is saved in a variable, don't I risk having more assignments and reads of values than with the for loop? – einpoklum Mar 11 '16 at 21:21
  • @einpoklum Then write `template class fixed_stride` that hard-codes the stride, if you want hard-coded compile-time strides? If you don't have hard-coded strides, but `stride` is clearly unchanged, the state of `stride` can be inlined to local and determined to be invariant and optimized out by a half-decent compiler. – Yakk - Adam Nevraumont Mar 11 '16 at 21:27
  • We'll see. Thanks. – einpoklum Mar 11 '16 at 21:39
  • @Yakk, I remember something about that as well. I think it was included in a new batch of things to deprecate/remove, so maybe STL is behind it if I'm actually remembering correctly. – chris Mar 12 '16 at 09:12
  • @Yakk, Thank you, it was p0174r0 where I saw it, and that probably came up on std-proposals. – chris Mar 12 '16 at 12:43