Reputation: 131515
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
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.
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