I am working with a code base where unfortunately I cannot replace all raw arrays with std::array
. The spec provides no guarantee that the memory layout of a std::array
is the same as a raw array meaning it can't be a drop in replacement in all situations (the data() method won't help you out if your code expects a pointer to a raw multidimensional array for example).
I have a lot of templates that are designed to work with Standard Library containers and I don't want to modify them to also accept raw arrays. To this end I have written a non-owning wrapper class for raw arrays which will allow them to be treated as a Standard Library container.
The interface attempts to mimic that of std::array
. I would appreciate any feedback on the implementation but specifically areas where it may not meet the expectation of somebody using a Standard Library container.
The code compiles under C++17.
namespace fibb
{
template <typename T, size_t N>
class Array_Wrapper
{
public:
/* TYPES */
using value_type = T;
using pointer = value_type*;
using const_pointer = const pointer;
using reference = value_type&;
using const_reference = const reference;
using size_type = size_t;
using difference_type = ptrdiff_t;
using iterator = pointer;
using const_iterator = const_pointer;
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
/* CONSTRUCTORS */
Array_Wrapper(T (&array_)[N]) // sized array
: m_array(array_)
{ }
Array_Wrapper(T*& array_) // decayed array pointer
: m_array(array_)
{ }
/* COMPARISON */
bool operator==(const Array_Wrapper& other) const { return m_array == other.m_array; }
bool operator!=(const Array_Wrapper& other) const { return m_array != other.m_array; }
/* FILL / SWAP */
void fill(const value_type& val) { std::fill_n(begin(), N, val); }
void swap(Array_Wrapper& other) noexcept(std::is_nothrow_swappable_v<value_type>)
{
if (*this != other)
{
std::swap_ranges(begin(), end(), other.begin());
}
}
void swap(std::array<value_type, N>& other) noexcept(std::is_nothrow_swappable_v<value_type>)
{
// swap ranges expects 3 iterators of the same type
// iterators for std::array are implementation defined
// the data() method always returns a raw ptr
std::swap_ranges(begin(), end(), other.data());
}
/* ITERATORS */
constexpr iterator begin() noexcept { return m_array; }
constexpr const_iterator begin() const noexcept { return m_array; }
constexpr const_iterator cbegin() const noexcept { return m_array; }
constexpr iterator end() noexcept { return m_array + N; }
constexpr const_iterator end() const noexcept { return m_array + N; }
constexpr const_iterator cend() const noexcept { return m_array + N; }
constexpr reverse_iterator rbegin() noexcept { return reverse_iterator(end()); }
constexpr const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator(cend()); }
constexpr const_reverse_iterator crbegin() const noexcept { return const_reverse_iterator(cend()); }
constexpr reverse_iterator rend() noexcept { return reverse_iterator(begin()); }
constexpr const_reverse_iterator rend() const noexcept { return const_reverse_iterator(cbegin()); }
constexpr const_reverse_iterator crend() const noexcept { return const_reverse_iterator(cbegin()); }
/* CAPACITY */
constexpr size_type size() const noexcept { return N; }
constexpr size_type max_size() const noexcept { return N; }
constexpr bool empty() const noexcept { return N; }
/* ELEMENT ACCESS */
constexpr reference operator[](size_type pos) { return m_array[pos]; }
constexpr const_reference operator[](size_type pos) const { return m_array[pos]; }
constexpr reference at(size_type pos)
{
if (pos >= N)
{
throw std::out_of_range(std::string("Out of range: ") + std::to_string(pos));
}
return m_array[pos];
}
constexpr const_reference at(size_type pos) const
{
if (pos >= N)
{
throw std::out_of_range(std::string("Out of range: ") + std::to_string(pos));
}
return m_array[pos];
}
constexpr reference front() { return m_array[0]; }
constexpr const_reference front() const { return m_array[0]; }
constexpr reference back() { return m_array[N - 1]; }
constexpr const_reference back() const { return m_array[N - 1]; }
constexpr pointer data() noexcept { return m_array; }
constexpr const_pointer data() const noexcept { return m_array; }
private:
pointer m_array;
};
}
Example construction:
constexpr size_t SIZE = 5;
int arr[SIZE] = {1, 3, 5, 7, 9};
fibb::Array_Wrapper wrap1(arr);
int* decayed_arr = arr;
fibb::Array_Wrapper<int, SIZE> wrap2(decayed_arr);
assert(&arr[0] == wrap1.begin() && &arr[0] == wrap2.begin());
EDIT:
Suggested corrections can be found on my personal github. The code has been abandoned in favour of gsl::span
as recommended.
ArrayWrapper::value
? Otherwise, I don't see much of a restriction for passing raw C-style arrays directly (like this). \$\endgroup\$ – hoffmale Jun 10 '18 at 23:31std::begin
andstd::end
require a sized array. \$\endgroup\$ – Fibbles Jun 10 '18 at 23:54begin()
andend()
methods rather than using the stand alone functions. In an ideal world it'd all be refactored but that's not possible at present. \$\endgroup\$ – Fibbles Jun 11 '18 at 0:25array_view
. You might find a complete implementation of one knocking about the ISO proposals and among that community. Also, Boost.Range v2 has an array wrapper; I don’t recall how general it is but it is specifically used for C strings that are decayed pointers. \$\endgroup\$ – JDługosz Jun 11 '18 at 3:07