Wrapper for a raw array so that it can be passed to templates expecting Standard Library containers

Clash Royale CLAN TAG#URR8PPP
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;
up vote
2
down vote
favorite
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.
c++ array collections c++17 wrapper
 |Â
show 3 more comments
up vote
2
down vote
favorite
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.
c++ array collections c++17 wrapper
Any specific reason you can't just pass the raw array to the algorithms instead? I.e. is the only reason for this wrapper to provide container trait types (likeArrayWrapper::value? Otherwise, I don't see much of a restriction for passing raw C-style arrays directly (like this).
â hoffmale
Jun 10 at 23:31
Unless I'm missing something you can't pass a decayed pointer into that?std::beginandstd::endrequire a sized array.
â Fibbles
Jun 10 at 23:54
That's why I asked for clarification, your explanations never mention decayed pointers (only the example, which I interpreted as a "nice to have" extra).
â hoffmale
Jun 11 at 0:17
Yeh sorry about that. It needs to work with decayed pointers. I'm also sure some of the code base is pre C++11 so expects things likebegin()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.
â Fibbles
Jun 11 at 0:25
1
It sounds like you want anarray_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.
â JDà Âugosz
Jun 11 at 3:07
 |Â
show 3 more comments
up vote
2
down vote
favorite
up vote
2
down vote
favorite
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.
c++ array collections c++17 wrapper
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.
c++ array collections c++17 wrapper
edited Jun 11 at 19:59
asked Jun 10 at 21:49
Fibbles
1407
1407
Any specific reason you can't just pass the raw array to the algorithms instead? I.e. is the only reason for this wrapper to provide container trait types (likeArrayWrapper::value? Otherwise, I don't see much of a restriction for passing raw C-style arrays directly (like this).
â hoffmale
Jun 10 at 23:31
Unless I'm missing something you can't pass a decayed pointer into that?std::beginandstd::endrequire a sized array.
â Fibbles
Jun 10 at 23:54
That's why I asked for clarification, your explanations never mention decayed pointers (only the example, which I interpreted as a "nice to have" extra).
â hoffmale
Jun 11 at 0:17
Yeh sorry about that. It needs to work with decayed pointers. I'm also sure some of the code base is pre C++11 so expects things likebegin()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.
â Fibbles
Jun 11 at 0:25
1
It sounds like you want anarray_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.
â JDà Âugosz
Jun 11 at 3:07
 |Â
show 3 more comments
Any specific reason you can't just pass the raw array to the algorithms instead? I.e. is the only reason for this wrapper to provide container trait types (likeArrayWrapper::value? Otherwise, I don't see much of a restriction for passing raw C-style arrays directly (like this).
â hoffmale
Jun 10 at 23:31
Unless I'm missing something you can't pass a decayed pointer into that?std::beginandstd::endrequire a sized array.
â Fibbles
Jun 10 at 23:54
That's why I asked for clarification, your explanations never mention decayed pointers (only the example, which I interpreted as a "nice to have" extra).
â hoffmale
Jun 11 at 0:17
Yeh sorry about that. It needs to work with decayed pointers. I'm also sure some of the code base is pre C++11 so expects things likebegin()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.
â Fibbles
Jun 11 at 0:25
1
It sounds like you want anarray_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.
â JDà Âugosz
Jun 11 at 3:07
Any specific reason you can't just pass the raw array to the algorithms instead? I.e. is the only reason for this wrapper to provide container trait types (like
ArrayWrapper::value? Otherwise, I don't see much of a restriction for passing raw C-style arrays directly (like this).â hoffmale
Jun 10 at 23:31
Any specific reason you can't just pass the raw array to the algorithms instead? I.e. is the only reason for this wrapper to provide container trait types (like
ArrayWrapper::value? Otherwise, I don't see much of a restriction for passing raw C-style arrays directly (like this).â hoffmale
Jun 10 at 23:31
Unless I'm missing something you can't pass a decayed pointer into that?
std::begin and std::end require a sized array.â Fibbles
Jun 10 at 23:54
Unless I'm missing something you can't pass a decayed pointer into that?
std::begin and std::end require a sized array.â Fibbles
Jun 10 at 23:54
That's why I asked for clarification, your explanations never mention decayed pointers (only the example, which I interpreted as a "nice to have" extra).
â hoffmale
Jun 11 at 0:17
That's why I asked for clarification, your explanations never mention decayed pointers (only the example, which I interpreted as a "nice to have" extra).
â hoffmale
Jun 11 at 0:17
Yeh sorry about that. It needs to work with decayed pointers. I'm also sure some of the code base is pre C++11 so expects things like
begin() and end() methods rather than using the stand alone functions. In an ideal world it'd all be refactored but that's not possible at present.â Fibbles
Jun 11 at 0:25
Yeh sorry about that. It needs to work with decayed pointers. I'm also sure some of the code base is pre C++11 so expects things like
begin() and end() methods rather than using the stand alone functions. In an ideal world it'd all be refactored but that's not possible at present.â Fibbles
Jun 11 at 0:25
1
1
It sounds like you want an
array_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.â JDà Âugosz
Jun 11 at 3:07
It sounds like you want an
array_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.â JDà Âugosz
Jun 11 at 3:07
 |Â
show 3 more comments
2 Answers
2
active
oldest
votes
up vote
3
down vote
accepted
Array_Wrapper::const_pointeris the same asT *const(const pointer toT), instead of the intendedconst T*(pointer toconst T). Same forArrayWrapper::const_reference. This allows the contents ofm_arrayto be modified viaconst_iteratororconst_referenceifTisn'tconstitself!All member functions (including constructors) could be marked
constexprin C++20. In C++17, onlyfilland both overloads forswapwouldn't work, sincestd::fill_nandstd::swap_rangearen'tconstexpryet.The implementation changes semantics for different operations. Comparison uses referential equality (= reference semantics), whereas swapping has value semantics (= swaps all values inside the arrays). Please choose one option, and stay consistent!
Many member functions have their preconditions violated if
N == 0. Maybe specialize forN = 0, or use SFINAE to hide invalid operations?Similar operations often include code duplication (e.g.
atoroperator==/operator!=). Try to extract common code, or try to implement one operation in terms of another.Nearly all member functions (except the overloads for
at) can be markednoexcept(though a lot are already).#includes are missing.Array_Wrapper::empty()returnsfalseifN == 0andtrueotherwise. This doesn't seem intended.I'd expect some documentation for
Array_Wrappers shortcomings, i.e.- it doesn't take ownership of the array!
- it doesn't work (nor is it intended to) with dynamically allocated arrays whose size is only known at runtime. (At least people used to
gsl::spanor the proposedstd::spanwould expect this functionality.)
Final note: Are there any issues with using gsl::span? It would give all the features in this implementation, plus some more (e.g. subspans, wrapping runtime arrays, ...).
1
Also, please run some exhaustive tests on your implementation. Many defects could have been caught by simple test cases. (Remember: "It compiles" is not sufficient if member functions in a class template aren't actually instantiated)
â hoffmale
Jun 11 at 4:18
Thanks for the feedback. I omitted the includes for brevity, they are there in the header. Likewise there is some brief documentation on non-ownership but individual method docs are non existent because the intent was to provide something that behaved as if it were astd::array. AFAIK zero sized arrays are illegal (even thoughstd::arrayhas a special case forN == 0). This is why there is no specific check for it. I have egg on my face over theempty()method and will be reviewing my unit tests. As forgsl::spanI wasn't aware of it previously but will have a good look at it.
â Fibbles
Jun 11 at 16:07
@Fibbles: The problem with "behaves asstd::array" is thatstd::arrayis directly responsible for memory management of the whole array (RAII). Since the wrapper does not provide this property, it should be explicitly mentioned (with all the same pitfalls likestd::string_view). And while there is no explicit provision for zero-length arrays in the standard, most compilers have an extension that supports them. If you don't want to support zero-length arrays in your wrapper, just make an empty definition forN == 0, like this:template<typename T> class Array_Wrapper<T, 0>;
â hoffmale
Jun 11 at 16:50
I have taken onboard your comments and tried to make the documentation more explicit in what features ofstd::arrayare mimicked and which are not. I had thought describing it as a "non-owning wrapper" was enough to clarify it was not responsible for memory management but you are right that it is better to be explicit. As for theN == 0case, I have just added a static assert to the constructors as it is more readable.
â Fibbles
Jun 11 at 17:08
I have made the amendments you recommended. They are available on my personal github. I won't be continuing with it further though as I have started usinggsl::spanas you suggested.
â Fibbles
Jun 11 at 19:56
add a comment |Â
up vote
1
down vote
I assume since this is being made to order that the semantics are designed for your specific use case. But, document that to be clear! In particular, it is odd that swap copies the elements like an owning container, as opposed to just swapping the references. Audit to make sure all semantics are as-intended and drawn from the real use case. Better to leave something unavailable (then add it once you find it is needed) then to get the use-case wrong.
Array_Wrapper(T (&array_)[N]) // sized array
: m_array(array_)
The current syntax is to use uniform initialization. That is, curly braces rather than parentheses for the member init list.
Array_Wrapper(T*& array_) // decayed array pointer
: m_array(array_)
I donâÂÂt see you doing anything with N here. You must specify it as an explicit template argument? I would have expected something more like gsl::span which includes a run-time length if it could not be determined at compile time.
Actually, is there a reason why gsl::span doesnâÂÂt do what you want?
void fill(const value_type& val) std::fill_n(begin(), N, val);
Fat interface: a generic range-aware fill should work on your type.
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];
The throw (and the string formatting!) generates a lot of code. The happy path is trivially inlined and optimized over. So keep the cold path out of the middle of it!
In general, put your throw in a helper function, and take precautions to make sure that helper does not get inlined, and the compiler knows it does not return. It can be shared among all template instantiations, too.
Also, (see e.g. MicrosoftâÂÂs GSL implementation) having the throw in one place makes it handy to replace the error behavior, add your clientâÂÂs logging system, etc.
constexpr reference at(size_type pos)
static_assert (std::is_unsigned_v<size_type> >) // instead of checking pos<0 too
if (pos >= N)
raise_range_error (pos, N);
return m_array[pos];
Just check out the code generated when you use (your original) at! shocking.
Thanks for the feedback. The swap semantics are as they are because the interface should behave as if it were astd::array. Swapping pointers in the wrapper would not alter the underlying arrays in any way which is not howstd::arrayworks. Similarlyfill()is there becausestd::arrayhas it. Specifying length at run time is not something needed at present but you are right that it is probably better to have the flexibility. @hoffmale also mentionedgsl::span. It's not something I was aware of but will definitely look into. Your comment on the exception handling was eye opening.
â Fibbles
Jun 11 at 16:20
add a comment |Â
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
3
down vote
accepted
Array_Wrapper::const_pointeris the same asT *const(const pointer toT), instead of the intendedconst T*(pointer toconst T). Same forArrayWrapper::const_reference. This allows the contents ofm_arrayto be modified viaconst_iteratororconst_referenceifTisn'tconstitself!All member functions (including constructors) could be marked
constexprin C++20. In C++17, onlyfilland both overloads forswapwouldn't work, sincestd::fill_nandstd::swap_rangearen'tconstexpryet.The implementation changes semantics for different operations. Comparison uses referential equality (= reference semantics), whereas swapping has value semantics (= swaps all values inside the arrays). Please choose one option, and stay consistent!
Many member functions have their preconditions violated if
N == 0. Maybe specialize forN = 0, or use SFINAE to hide invalid operations?Similar operations often include code duplication (e.g.
atoroperator==/operator!=). Try to extract common code, or try to implement one operation in terms of another.Nearly all member functions (except the overloads for
at) can be markednoexcept(though a lot are already).#includes are missing.Array_Wrapper::empty()returnsfalseifN == 0andtrueotherwise. This doesn't seem intended.I'd expect some documentation for
Array_Wrappers shortcomings, i.e.- it doesn't take ownership of the array!
- it doesn't work (nor is it intended to) with dynamically allocated arrays whose size is only known at runtime. (At least people used to
gsl::spanor the proposedstd::spanwould expect this functionality.)
Final note: Are there any issues with using gsl::span? It would give all the features in this implementation, plus some more (e.g. subspans, wrapping runtime arrays, ...).
1
Also, please run some exhaustive tests on your implementation. Many defects could have been caught by simple test cases. (Remember: "It compiles" is not sufficient if member functions in a class template aren't actually instantiated)
â hoffmale
Jun 11 at 4:18
Thanks for the feedback. I omitted the includes for brevity, they are there in the header. Likewise there is some brief documentation on non-ownership but individual method docs are non existent because the intent was to provide something that behaved as if it were astd::array. AFAIK zero sized arrays are illegal (even thoughstd::arrayhas a special case forN == 0). This is why there is no specific check for it. I have egg on my face over theempty()method and will be reviewing my unit tests. As forgsl::spanI wasn't aware of it previously but will have a good look at it.
â Fibbles
Jun 11 at 16:07
@Fibbles: The problem with "behaves asstd::array" is thatstd::arrayis directly responsible for memory management of the whole array (RAII). Since the wrapper does not provide this property, it should be explicitly mentioned (with all the same pitfalls likestd::string_view). And while there is no explicit provision for zero-length arrays in the standard, most compilers have an extension that supports them. If you don't want to support zero-length arrays in your wrapper, just make an empty definition forN == 0, like this:template<typename T> class Array_Wrapper<T, 0>;
â hoffmale
Jun 11 at 16:50
I have taken onboard your comments and tried to make the documentation more explicit in what features ofstd::arrayare mimicked and which are not. I had thought describing it as a "non-owning wrapper" was enough to clarify it was not responsible for memory management but you are right that it is better to be explicit. As for theN == 0case, I have just added a static assert to the constructors as it is more readable.
â Fibbles
Jun 11 at 17:08
I have made the amendments you recommended. They are available on my personal github. I won't be continuing with it further though as I have started usinggsl::spanas you suggested.
â Fibbles
Jun 11 at 19:56
add a comment |Â
up vote
3
down vote
accepted
Array_Wrapper::const_pointeris the same asT *const(const pointer toT), instead of the intendedconst T*(pointer toconst T). Same forArrayWrapper::const_reference. This allows the contents ofm_arrayto be modified viaconst_iteratororconst_referenceifTisn'tconstitself!All member functions (including constructors) could be marked
constexprin C++20. In C++17, onlyfilland both overloads forswapwouldn't work, sincestd::fill_nandstd::swap_rangearen'tconstexpryet.The implementation changes semantics for different operations. Comparison uses referential equality (= reference semantics), whereas swapping has value semantics (= swaps all values inside the arrays). Please choose one option, and stay consistent!
Many member functions have their preconditions violated if
N == 0. Maybe specialize forN = 0, or use SFINAE to hide invalid operations?Similar operations often include code duplication (e.g.
atoroperator==/operator!=). Try to extract common code, or try to implement one operation in terms of another.Nearly all member functions (except the overloads for
at) can be markednoexcept(though a lot are already).#includes are missing.Array_Wrapper::empty()returnsfalseifN == 0andtrueotherwise. This doesn't seem intended.I'd expect some documentation for
Array_Wrappers shortcomings, i.e.- it doesn't take ownership of the array!
- it doesn't work (nor is it intended to) with dynamically allocated arrays whose size is only known at runtime. (At least people used to
gsl::spanor the proposedstd::spanwould expect this functionality.)
Final note: Are there any issues with using gsl::span? It would give all the features in this implementation, plus some more (e.g. subspans, wrapping runtime arrays, ...).
1
Also, please run some exhaustive tests on your implementation. Many defects could have been caught by simple test cases. (Remember: "It compiles" is not sufficient if member functions in a class template aren't actually instantiated)
â hoffmale
Jun 11 at 4:18
Thanks for the feedback. I omitted the includes for brevity, they are there in the header. Likewise there is some brief documentation on non-ownership but individual method docs are non existent because the intent was to provide something that behaved as if it were astd::array. AFAIK zero sized arrays are illegal (even thoughstd::arrayhas a special case forN == 0). This is why there is no specific check for it. I have egg on my face over theempty()method and will be reviewing my unit tests. As forgsl::spanI wasn't aware of it previously but will have a good look at it.
â Fibbles
Jun 11 at 16:07
@Fibbles: The problem with "behaves asstd::array" is thatstd::arrayis directly responsible for memory management of the whole array (RAII). Since the wrapper does not provide this property, it should be explicitly mentioned (with all the same pitfalls likestd::string_view). And while there is no explicit provision for zero-length arrays in the standard, most compilers have an extension that supports them. If you don't want to support zero-length arrays in your wrapper, just make an empty definition forN == 0, like this:template<typename T> class Array_Wrapper<T, 0>;
â hoffmale
Jun 11 at 16:50
I have taken onboard your comments and tried to make the documentation more explicit in what features ofstd::arrayare mimicked and which are not. I had thought describing it as a "non-owning wrapper" was enough to clarify it was not responsible for memory management but you are right that it is better to be explicit. As for theN == 0case, I have just added a static assert to the constructors as it is more readable.
â Fibbles
Jun 11 at 17:08
I have made the amendments you recommended. They are available on my personal github. I won't be continuing with it further though as I have started usinggsl::spanas you suggested.
â Fibbles
Jun 11 at 19:56
add a comment |Â
up vote
3
down vote
accepted
up vote
3
down vote
accepted
Array_Wrapper::const_pointeris the same asT *const(const pointer toT), instead of the intendedconst T*(pointer toconst T). Same forArrayWrapper::const_reference. This allows the contents ofm_arrayto be modified viaconst_iteratororconst_referenceifTisn'tconstitself!All member functions (including constructors) could be marked
constexprin C++20. In C++17, onlyfilland both overloads forswapwouldn't work, sincestd::fill_nandstd::swap_rangearen'tconstexpryet.The implementation changes semantics for different operations. Comparison uses referential equality (= reference semantics), whereas swapping has value semantics (= swaps all values inside the arrays). Please choose one option, and stay consistent!
Many member functions have their preconditions violated if
N == 0. Maybe specialize forN = 0, or use SFINAE to hide invalid operations?Similar operations often include code duplication (e.g.
atoroperator==/operator!=). Try to extract common code, or try to implement one operation in terms of another.Nearly all member functions (except the overloads for
at) can be markednoexcept(though a lot are already).#includes are missing.Array_Wrapper::empty()returnsfalseifN == 0andtrueotherwise. This doesn't seem intended.I'd expect some documentation for
Array_Wrappers shortcomings, i.e.- it doesn't take ownership of the array!
- it doesn't work (nor is it intended to) with dynamically allocated arrays whose size is only known at runtime. (At least people used to
gsl::spanor the proposedstd::spanwould expect this functionality.)
Final note: Are there any issues with using gsl::span? It would give all the features in this implementation, plus some more (e.g. subspans, wrapping runtime arrays, ...).
Array_Wrapper::const_pointeris the same asT *const(const pointer toT), instead of the intendedconst T*(pointer toconst T). Same forArrayWrapper::const_reference. This allows the contents ofm_arrayto be modified viaconst_iteratororconst_referenceifTisn'tconstitself!All member functions (including constructors) could be marked
constexprin C++20. In C++17, onlyfilland both overloads forswapwouldn't work, sincestd::fill_nandstd::swap_rangearen'tconstexpryet.The implementation changes semantics for different operations. Comparison uses referential equality (= reference semantics), whereas swapping has value semantics (= swaps all values inside the arrays). Please choose one option, and stay consistent!
Many member functions have their preconditions violated if
N == 0. Maybe specialize forN = 0, or use SFINAE to hide invalid operations?Similar operations often include code duplication (e.g.
atoroperator==/operator!=). Try to extract common code, or try to implement one operation in terms of another.Nearly all member functions (except the overloads for
at) can be markednoexcept(though a lot are already).#includes are missing.Array_Wrapper::empty()returnsfalseifN == 0andtrueotherwise. This doesn't seem intended.I'd expect some documentation for
Array_Wrappers shortcomings, i.e.- it doesn't take ownership of the array!
- it doesn't work (nor is it intended to) with dynamically allocated arrays whose size is only known at runtime. (At least people used to
gsl::spanor the proposedstd::spanwould expect this functionality.)
Final note: Are there any issues with using gsl::span? It would give all the features in this implementation, plus some more (e.g. subspans, wrapping runtime arrays, ...).
edited Jun 11 at 12:02
answered Jun 11 at 3:56
hoffmale
4,235630
4,235630
1
Also, please run some exhaustive tests on your implementation. Many defects could have been caught by simple test cases. (Remember: "It compiles" is not sufficient if member functions in a class template aren't actually instantiated)
â hoffmale
Jun 11 at 4:18
Thanks for the feedback. I omitted the includes for brevity, they are there in the header. Likewise there is some brief documentation on non-ownership but individual method docs are non existent because the intent was to provide something that behaved as if it were astd::array. AFAIK zero sized arrays are illegal (even thoughstd::arrayhas a special case forN == 0). This is why there is no specific check for it. I have egg on my face over theempty()method and will be reviewing my unit tests. As forgsl::spanI wasn't aware of it previously but will have a good look at it.
â Fibbles
Jun 11 at 16:07
@Fibbles: The problem with "behaves asstd::array" is thatstd::arrayis directly responsible for memory management of the whole array (RAII). Since the wrapper does not provide this property, it should be explicitly mentioned (with all the same pitfalls likestd::string_view). And while there is no explicit provision for zero-length arrays in the standard, most compilers have an extension that supports them. If you don't want to support zero-length arrays in your wrapper, just make an empty definition forN == 0, like this:template<typename T> class Array_Wrapper<T, 0>;
â hoffmale
Jun 11 at 16:50
I have taken onboard your comments and tried to make the documentation more explicit in what features ofstd::arrayare mimicked and which are not. I had thought describing it as a "non-owning wrapper" was enough to clarify it was not responsible for memory management but you are right that it is better to be explicit. As for theN == 0case, I have just added a static assert to the constructors as it is more readable.
â Fibbles
Jun 11 at 17:08
I have made the amendments you recommended. They are available on my personal github. I won't be continuing with it further though as I have started usinggsl::spanas you suggested.
â Fibbles
Jun 11 at 19:56
add a comment |Â
1
Also, please run some exhaustive tests on your implementation. Many defects could have been caught by simple test cases. (Remember: "It compiles" is not sufficient if member functions in a class template aren't actually instantiated)
â hoffmale
Jun 11 at 4:18
Thanks for the feedback. I omitted the includes for brevity, they are there in the header. Likewise there is some brief documentation on non-ownership but individual method docs are non existent because the intent was to provide something that behaved as if it were astd::array. AFAIK zero sized arrays are illegal (even thoughstd::arrayhas a special case forN == 0). This is why there is no specific check for it. I have egg on my face over theempty()method and will be reviewing my unit tests. As forgsl::spanI wasn't aware of it previously but will have a good look at it.
â Fibbles
Jun 11 at 16:07
@Fibbles: The problem with "behaves asstd::array" is thatstd::arrayis directly responsible for memory management of the whole array (RAII). Since the wrapper does not provide this property, it should be explicitly mentioned (with all the same pitfalls likestd::string_view). And while there is no explicit provision for zero-length arrays in the standard, most compilers have an extension that supports them. If you don't want to support zero-length arrays in your wrapper, just make an empty definition forN == 0, like this:template<typename T> class Array_Wrapper<T, 0>;
â hoffmale
Jun 11 at 16:50
I have taken onboard your comments and tried to make the documentation more explicit in what features ofstd::arrayare mimicked and which are not. I had thought describing it as a "non-owning wrapper" was enough to clarify it was not responsible for memory management but you are right that it is better to be explicit. As for theN == 0case, I have just added a static assert to the constructors as it is more readable.
â Fibbles
Jun 11 at 17:08
I have made the amendments you recommended. They are available on my personal github. I won't be continuing with it further though as I have started usinggsl::spanas you suggested.
â Fibbles
Jun 11 at 19:56
1
1
Also, please run some exhaustive tests on your implementation. Many defects could have been caught by simple test cases. (Remember: "It compiles" is not sufficient if member functions in a class template aren't actually instantiated)
â hoffmale
Jun 11 at 4:18
Also, please run some exhaustive tests on your implementation. Many defects could have been caught by simple test cases. (Remember: "It compiles" is not sufficient if member functions in a class template aren't actually instantiated)
â hoffmale
Jun 11 at 4:18
Thanks for the feedback. I omitted the includes for brevity, they are there in the header. Likewise there is some brief documentation on non-ownership but individual method docs are non existent because the intent was to provide something that behaved as if it were a
std::array. AFAIK zero sized arrays are illegal (even though std::array has a special case for N == 0). This is why there is no specific check for it. I have egg on my face over the empty() method and will be reviewing my unit tests. As for gsl::span I wasn't aware of it previously but will have a good look at it.â Fibbles
Jun 11 at 16:07
Thanks for the feedback. I omitted the includes for brevity, they are there in the header. Likewise there is some brief documentation on non-ownership but individual method docs are non existent because the intent was to provide something that behaved as if it were a
std::array. AFAIK zero sized arrays are illegal (even though std::array has a special case for N == 0). This is why there is no specific check for it. I have egg on my face over the empty() method and will be reviewing my unit tests. As for gsl::span I wasn't aware of it previously but will have a good look at it.â Fibbles
Jun 11 at 16:07
@Fibbles: The problem with "behaves as
std::array" is that std::array is directly responsible for memory management of the whole array (RAII). Since the wrapper does not provide this property, it should be explicitly mentioned (with all the same pitfalls like std::string_view). And while there is no explicit provision for zero-length arrays in the standard, most compilers have an extension that supports them. If you don't want to support zero-length arrays in your wrapper, just make an empty definition for N == 0, like this: template<typename T> class Array_Wrapper<T, 0>;â hoffmale
Jun 11 at 16:50
@Fibbles: The problem with "behaves as
std::array" is that std::array is directly responsible for memory management of the whole array (RAII). Since the wrapper does not provide this property, it should be explicitly mentioned (with all the same pitfalls like std::string_view). And while there is no explicit provision for zero-length arrays in the standard, most compilers have an extension that supports them. If you don't want to support zero-length arrays in your wrapper, just make an empty definition for N == 0, like this: template<typename T> class Array_Wrapper<T, 0>;â hoffmale
Jun 11 at 16:50
I have taken onboard your comments and tried to make the documentation more explicit in what features of
std::array are mimicked and which are not. I had thought describing it as a "non-owning wrapper" was enough to clarify it was not responsible for memory management but you are right that it is better to be explicit. As for the N == 0 case, I have just added a static assert to the constructors as it is more readable.â Fibbles
Jun 11 at 17:08
I have taken onboard your comments and tried to make the documentation more explicit in what features of
std::array are mimicked and which are not. I had thought describing it as a "non-owning wrapper" was enough to clarify it was not responsible for memory management but you are right that it is better to be explicit. As for the N == 0 case, I have just added a static assert to the constructors as it is more readable.â Fibbles
Jun 11 at 17:08
I have made the amendments you recommended. They are available on my personal github. I won't be continuing with it further though as I have started using
gsl::span as you suggested.â Fibbles
Jun 11 at 19:56
I have made the amendments you recommended. They are available on my personal github. I won't be continuing with it further though as I have started using
gsl::span as you suggested.â Fibbles
Jun 11 at 19:56
add a comment |Â
up vote
1
down vote
I assume since this is being made to order that the semantics are designed for your specific use case. But, document that to be clear! In particular, it is odd that swap copies the elements like an owning container, as opposed to just swapping the references. Audit to make sure all semantics are as-intended and drawn from the real use case. Better to leave something unavailable (then add it once you find it is needed) then to get the use-case wrong.
Array_Wrapper(T (&array_)[N]) // sized array
: m_array(array_)
The current syntax is to use uniform initialization. That is, curly braces rather than parentheses for the member init list.
Array_Wrapper(T*& array_) // decayed array pointer
: m_array(array_)
I donâÂÂt see you doing anything with N here. You must specify it as an explicit template argument? I would have expected something more like gsl::span which includes a run-time length if it could not be determined at compile time.
Actually, is there a reason why gsl::span doesnâÂÂt do what you want?
void fill(const value_type& val) std::fill_n(begin(), N, val);
Fat interface: a generic range-aware fill should work on your type.
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];
The throw (and the string formatting!) generates a lot of code. The happy path is trivially inlined and optimized over. So keep the cold path out of the middle of it!
In general, put your throw in a helper function, and take precautions to make sure that helper does not get inlined, and the compiler knows it does not return. It can be shared among all template instantiations, too.
Also, (see e.g. MicrosoftâÂÂs GSL implementation) having the throw in one place makes it handy to replace the error behavior, add your clientâÂÂs logging system, etc.
constexpr reference at(size_type pos)
static_assert (std::is_unsigned_v<size_type> >) // instead of checking pos<0 too
if (pos >= N)
raise_range_error (pos, N);
return m_array[pos];
Just check out the code generated when you use (your original) at! shocking.
Thanks for the feedback. The swap semantics are as they are because the interface should behave as if it were astd::array. Swapping pointers in the wrapper would not alter the underlying arrays in any way which is not howstd::arrayworks. Similarlyfill()is there becausestd::arrayhas it. Specifying length at run time is not something needed at present but you are right that it is probably better to have the flexibility. @hoffmale also mentionedgsl::span. It's not something I was aware of but will definitely look into. Your comment on the exception handling was eye opening.
â Fibbles
Jun 11 at 16:20
add a comment |Â
up vote
1
down vote
I assume since this is being made to order that the semantics are designed for your specific use case. But, document that to be clear! In particular, it is odd that swap copies the elements like an owning container, as opposed to just swapping the references. Audit to make sure all semantics are as-intended and drawn from the real use case. Better to leave something unavailable (then add it once you find it is needed) then to get the use-case wrong.
Array_Wrapper(T (&array_)[N]) // sized array
: m_array(array_)
The current syntax is to use uniform initialization. That is, curly braces rather than parentheses for the member init list.
Array_Wrapper(T*& array_) // decayed array pointer
: m_array(array_)
I donâÂÂt see you doing anything with N here. You must specify it as an explicit template argument? I would have expected something more like gsl::span which includes a run-time length if it could not be determined at compile time.
Actually, is there a reason why gsl::span doesnâÂÂt do what you want?
void fill(const value_type& val) std::fill_n(begin(), N, val);
Fat interface: a generic range-aware fill should work on your type.
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];
The throw (and the string formatting!) generates a lot of code. The happy path is trivially inlined and optimized over. So keep the cold path out of the middle of it!
In general, put your throw in a helper function, and take precautions to make sure that helper does not get inlined, and the compiler knows it does not return. It can be shared among all template instantiations, too.
Also, (see e.g. MicrosoftâÂÂs GSL implementation) having the throw in one place makes it handy to replace the error behavior, add your clientâÂÂs logging system, etc.
constexpr reference at(size_type pos)
static_assert (std::is_unsigned_v<size_type> >) // instead of checking pos<0 too
if (pos >= N)
raise_range_error (pos, N);
return m_array[pos];
Just check out the code generated when you use (your original) at! shocking.
Thanks for the feedback. The swap semantics are as they are because the interface should behave as if it were astd::array. Swapping pointers in the wrapper would not alter the underlying arrays in any way which is not howstd::arrayworks. Similarlyfill()is there becausestd::arrayhas it. Specifying length at run time is not something needed at present but you are right that it is probably better to have the flexibility. @hoffmale also mentionedgsl::span. It's not something I was aware of but will definitely look into. Your comment on the exception handling was eye opening.
â Fibbles
Jun 11 at 16:20
add a comment |Â
up vote
1
down vote
up vote
1
down vote
I assume since this is being made to order that the semantics are designed for your specific use case. But, document that to be clear! In particular, it is odd that swap copies the elements like an owning container, as opposed to just swapping the references. Audit to make sure all semantics are as-intended and drawn from the real use case. Better to leave something unavailable (then add it once you find it is needed) then to get the use-case wrong.
Array_Wrapper(T (&array_)[N]) // sized array
: m_array(array_)
The current syntax is to use uniform initialization. That is, curly braces rather than parentheses for the member init list.
Array_Wrapper(T*& array_) // decayed array pointer
: m_array(array_)
I donâÂÂt see you doing anything with N here. You must specify it as an explicit template argument? I would have expected something more like gsl::span which includes a run-time length if it could not be determined at compile time.
Actually, is there a reason why gsl::span doesnâÂÂt do what you want?
void fill(const value_type& val) std::fill_n(begin(), N, val);
Fat interface: a generic range-aware fill should work on your type.
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];
The throw (and the string formatting!) generates a lot of code. The happy path is trivially inlined and optimized over. So keep the cold path out of the middle of it!
In general, put your throw in a helper function, and take precautions to make sure that helper does not get inlined, and the compiler knows it does not return. It can be shared among all template instantiations, too.
Also, (see e.g. MicrosoftâÂÂs GSL implementation) having the throw in one place makes it handy to replace the error behavior, add your clientâÂÂs logging system, etc.
constexpr reference at(size_type pos)
static_assert (std::is_unsigned_v<size_type> >) // instead of checking pos<0 too
if (pos >= N)
raise_range_error (pos, N);
return m_array[pos];
Just check out the code generated when you use (your original) at! shocking.
I assume since this is being made to order that the semantics are designed for your specific use case. But, document that to be clear! In particular, it is odd that swap copies the elements like an owning container, as opposed to just swapping the references. Audit to make sure all semantics are as-intended and drawn from the real use case. Better to leave something unavailable (then add it once you find it is needed) then to get the use-case wrong.
Array_Wrapper(T (&array_)[N]) // sized array
: m_array(array_)
The current syntax is to use uniform initialization. That is, curly braces rather than parentheses for the member init list.
Array_Wrapper(T*& array_) // decayed array pointer
: m_array(array_)
I donâÂÂt see you doing anything with N here. You must specify it as an explicit template argument? I would have expected something more like gsl::span which includes a run-time length if it could not be determined at compile time.
Actually, is there a reason why gsl::span doesnâÂÂt do what you want?
void fill(const value_type& val) std::fill_n(begin(), N, val);
Fat interface: a generic range-aware fill should work on your type.
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];
The throw (and the string formatting!) generates a lot of code. The happy path is trivially inlined and optimized over. So keep the cold path out of the middle of it!
In general, put your throw in a helper function, and take precautions to make sure that helper does not get inlined, and the compiler knows it does not return. It can be shared among all template instantiations, too.
Also, (see e.g. MicrosoftâÂÂs GSL implementation) having the throw in one place makes it handy to replace the error behavior, add your clientâÂÂs logging system, etc.
constexpr reference at(size_type pos)
static_assert (std::is_unsigned_v<size_type> >) // instead of checking pos<0 too
if (pos >= N)
raise_range_error (pos, N);
return m_array[pos];
Just check out the code generated when you use (your original) at! shocking.
answered Jun 11 at 3:27
JDÃ Âugosz
5,047731
5,047731
Thanks for the feedback. The swap semantics are as they are because the interface should behave as if it were astd::array. Swapping pointers in the wrapper would not alter the underlying arrays in any way which is not howstd::arrayworks. Similarlyfill()is there becausestd::arrayhas it. Specifying length at run time is not something needed at present but you are right that it is probably better to have the flexibility. @hoffmale also mentionedgsl::span. It's not something I was aware of but will definitely look into. Your comment on the exception handling was eye opening.
â Fibbles
Jun 11 at 16:20
add a comment |Â
Thanks for the feedback. The swap semantics are as they are because the interface should behave as if it were astd::array. Swapping pointers in the wrapper would not alter the underlying arrays in any way which is not howstd::arrayworks. Similarlyfill()is there becausestd::arrayhas it. Specifying length at run time is not something needed at present but you are right that it is probably better to have the flexibility. @hoffmale also mentionedgsl::span. It's not something I was aware of but will definitely look into. Your comment on the exception handling was eye opening.
â Fibbles
Jun 11 at 16:20
Thanks for the feedback. The swap semantics are as they are because the interface should behave as if it were a
std::array. Swapping pointers in the wrapper would not alter the underlying arrays in any way which is not how std::array works. Similarly fill() is there because std::array has it. Specifying length at run time is not something needed at present but you are right that it is probably better to have the flexibility. @hoffmale also mentioned gsl::span. It's not something I was aware of but will definitely look into. Your comment on the exception handling was eye opening.â Fibbles
Jun 11 at 16:20
Thanks for the feedback. The swap semantics are as they are because the interface should behave as if it were a
std::array. Swapping pointers in the wrapper would not alter the underlying arrays in any way which is not how std::array works. Similarly fill() is there because std::array has it. Specifying length at run time is not something needed at present but you are right that it is probably better to have the flexibility. @hoffmale also mentioned gsl::span. It's not something I was aware of but will definitely look into. Your comment on the exception handling was eye opening.â Fibbles
Jun 11 at 16:20
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f196246%2fwrapper-for-a-raw-array-so-that-it-can-be-passed-to-templates-expecting-standard%23new-answer', 'question_page');
);
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Any specific reason you can't just pass the raw array to the algorithms instead? I.e. is the only reason for this wrapper to provide container trait types (like
ArrayWrapper::value? Otherwise, I don't see much of a restriction for passing raw C-style arrays directly (like this).â hoffmale
Jun 10 at 23:31
Unless I'm missing something you can't pass a decayed pointer into that?
std::beginandstd::endrequire a sized array.â Fibbles
Jun 10 at 23:54
That's why I asked for clarification, your explanations never mention decayed pointers (only the example, which I interpreted as a "nice to have" extra).
â hoffmale
Jun 11 at 0:17
Yeh sorry about that. It needs to work with decayed pointers. I'm also sure some of the code base is pre C++11 so expects things like
begin()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.â Fibbles
Jun 11 at 0:25
1
It sounds like you want an
array_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.â JDà Âugosz
Jun 11 at 3:07