This can be done, but it takes a few steps to do cleanly. First, write a template class
that represents a range of contiguous values. Then forward a template
version that knows how big the array
is to the Impl
version that takes this contiguous range.
Finally, implement the contig_range
version. Note that for( int& x: range )
works for contig_range
, because I implemented begin()
and end()
and pointers are iterators.
template<typename T>
struct contig_range {
T* _begin, _end;
contig_range( T* b, T* e ):_begin(b), _end(e) {}
T const* begin() const { return _begin; }
T const* end() const { return _end; }
T* begin() { return _begin; }
T* end() { return _end; }
contig_range( contig_range const& ) = default;
contig_range( contig_range && ) = default;
contig_range():_begin(nullptr), _end(nullptr) {}
// maybe block `operator=`? contig_range follows reference semantics
// and there really isn't a run time safe `operator=` for reference semantics on
// a range when the RHS is of unknown width...
// I guess I could make it follow pointer semantics and rebase? Dunno
// this being tricky, I am tempted to =delete operator=
template<typename T, std::size_t N>
contig_range( std::array<T, N>& arr ): _begin(&*std::begin(arr)), _end(&*std::end(arr)) {}
template<typename T, std::size_t N>
contig_range( T(&arr)[N] ): _begin(&*std::begin(arr)), _end(&*std::end(arr)) {}
template<typename T, typename A>
contig_range( std::vector<T, A>& arr ): _begin(&*std::begin(arr)), _end(&*std::end(arr)) {}
};
void mulArrayImpl( contig_range<int> arr, const int multiplier );
template<std::size_t N>
void mulArray( std::array<int, N>& arr, const int multiplier ) {
mulArrayImpl( contig_range<int>(arr), multiplier );
}
(not tested, but design should work).
Then, in your .cpp
file:
void mulArrayImpl(contig_range<int> rng, const int multiplier) {
for(auto& e : rng) {
e *= multiplier;
}
}
This has the downside that the code that loops over the contents of the array doesn't know (at compile time) how big the array is, which could cost optimization. It has the advantage that the implementation doesn't have to be in the header.
Be careful about explicitly constructing a contig_range
, as if you pass it a set
it will assume that the set
data is contiguous, which is false, and do undefined behavior all over the place. The only two std
containers that this is guaranteed to work on are vector
and array
(and C-style arrays, as it happens!). deque
despite being random access isn't contiguous (dangerously, it is contiguous in small chunks!), list
is not even close, and the associative (ordered and unordered) containers are equally non-contiguous.
So the three constructors I implemented where std::array
, std::vector
and C-style arrays, which basically covers the bases.
Implementing []
is easy as well, and between for()
and []
that is most of what you want an array
for, isn't it?