If you don't need a container, don't use a container. Use an iterable range.
A container is both an iterable range, and an owner of its contents.
In the case of a vector
, it is a contiguous iterable range and an owner of the contents. Odds are you only need to know if is a random access iterable range in your implementation code.
However, dealing with arbitrary contiguous iterable ranges has a cost, in that you have to put your implementation in a template header file, or do expensive type erasure. Two ways to approach this is to use the fact that you are only accepting sub-ranges of a vector
, or use the fact that you are only accepting sub-ranges of a contiguous range.
I like the contiguous range idea myself:
template<typename T>
struct contiguous_range {
T* b; T* e;
contiguous_range( contiguous_range const& ) = default;
contiguous_range():b(nullptr), e(nullptr) {};
contiguous_range& operator=( contiguous_range const& ) = default;
std::size_t size() const { return e-b; }
T* begin() const { return b; } // note, T*
T* end() const { return e; } // note, T*
template<typename U, typename=typename std::enable_if<
sizeof(U)==sizeof(T)
&& std::is_convertible<U*, T*>::value
>::type>
contiguous_range( contiguous_range<U> const& o ):b(o.b), e(o.e) {};
T& operator[]( std::size_t i ) const { return b[i]; }
template<typename A>
contiguous_range( std::vector<T, A> const& v ):b(v.data()), e(v.data()+v.size()) {}
template<typename U, std::size_t N, typename=typename std::enable_if<
sizeof(U)==sizeof(T)
&& std::is_convertible<U*, T*>::value
>::type>
contiguous_range( std::array<U, N> const& a ):b(a.data()), e(a.data()+a.size()) {}
template<typename U, std::size_t N, typename=typename std::enable_if<
sizeof(U)==sizeof(T)
&& std::is_convertible<U*, T*>::value
>::type>
contiguous_range( U(&a)[N] ):b(&a[0]), e((&a[0])+N) {}
template<typename U, typename=typename std::enable_if<
sizeof(U)==sizeof(T)
&& std::is_convertible<U*, T*>::value
>::type>
contiguous_range( U* b_, U* e_ ):b(b_), e(e_) {}
};
template<typename I>
auto contiguous_subrange( I b, I e )
-> contiguous_range<std::iterator_traits<I>::value_type>
{
return {&*b, &*e};
}
template<typename C>
auto contiguous_subrange( C&& c, std::size_t start, std::size_t end )
-> decltype( contiguous_subrange( &c[start], &c[end] ) )
{ return ( contiguous_subrange( &c[start], &c[end] ) ) };
Now, our functions can simply take a contiguous_range<int>
or continguos_range<const int>
, and they can be implicitly passed a std::vector<int>
.
You can also set up subranges of your std::vector
that are equally contiguous.
Note that a constiguous_range<int>
corresponds to a std::vector<int>&
, and a contiguous_range<const int>
corresponds to a std::vector<int> const&
.