Quantcast
Channel: Simple multi-dimensional Array class in C++11 - Code Review Stack Exchange
Viewing all articles
Browse latest Browse all 2

Answer by Barry for Simple multi-dimensional Array class in C++11

$
0
0

I think your class isn't nearly as useful enough as it could be due to your choices in member variables. Furthermore, it's less efficient than it could be. Also the code is written in a style that overcomplicates the problem.

Member Variables

Your members are:

std::unique_ptr<ElementType[]>ElementType* constconst std::array<SizeType, Dimensions>const SizeTypeconst std::array<SizeType, Dimensions>

This choice makes the class noncopyable and nonassignable. But why? There's nothing inherent about a multidimensional array that suggests it shouldn't be assignable or copyable. You make some members public. There's no reason to do that. Particularly bad is data - which is redundant with _dataOwner.

You should strive to make your class as generic as possible. To that end I suggest you simply have two members, both private:

ElementType* data;std::array<size_t, Dimensions> dimensions;

You can derive dataLength and indexCoeffs from dimensions if need be, and since you'd have to iterate over the array to do anything anyway, I don't see what precomputing saves you.

This also allows you do support copying and moving.

Forwarding References

Forwarding references are a great choice for function templates when you can take objects by lvalue or rvalue and do the cheapest correct thing possible in all cases. However, everywhere that you are using them, the objects getting past in must be integral types (I don't see you checking this, but you should). There is no different between copying and moving an integral type, so simply take everything by value. That saves you from having to do all of the std::forward<>-ing. For example:

template <class... Indices>ElementType& at(Indices... indices){    return data[rawIndex(indices...)];}

Bounds Checking

You introduce a macro for whether or not to do bounds checking. However, convention from the standard library suggests that we just provide functions that DO range checking and functions that don't. at() should throw std::out_of_range, and operator() should never throw:

template <typename... Indices>ElementType& operator()(Indices... indices) {    // nothrow implementation}template <typename... Indices>ElementType& at(Indices... indices){    some_range_checking(indices...);    return (*this)(indices...);}

Compile time checking

First, a cleaner way to write are_integral would be to use the bool_pack trick:

template <bool... > struct bool_pack { };template <bool... b>using all_true = std::is_same<bool_pack<true, b...>, bool_pack<b..., true>>;

With:

template <typename... T>using are_all_integral = all_true<std::is_integral<T>::value...>;

And you should actually use that metafunction as part of the signature of every function! That is preferred to a simple static_assert since any reflection-style operations on your class would actually yield the correct result:

template <typename.... DimensionLengths,          typename = std::enable_if_t<are_all_integral<DimensionLengths...>::value && sizeof...(DimensionLengths) == Dimensions>>array(DimensionLengths... ){ ... }

Otherwise, you would get something weird like std::is_constructible<array<int, 4>, std::string> being true.

Iterators

An important part of writing a container is writing iterators for it. You may just provide general iterators that just go over the whole array front to end. Or you may want to support arrays that iterate over a single dimension and provide a proxy object to a multi-dimensional array of one dimension less. Either would be good to have.


Viewing all articles
Browse latest Browse all 2

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>