4
\$\begingroup\$

This is a followup to a previous question of mine on same subject.

Context

I want to have a tool that allows to simulate as much as possible a C++ standard container for a multidimensional contiguous array whose sizes are only known at run time. That immediately means that std::arrays are off (compile time sizes), as are vectors of vectors (not contiguous data). That also means that the common solution multi_array(i, j, k) is also off because of no natural iterators and direct members.

Previous review have shown that is is impossible to build conformant containers and iterators over views of data (Refs: When Is a Container Not a Container? and To Be or Not to Be (an Iterator)), so I have followed the given advice and tried to follow the proxy pattern. I have also tried to better explain in comments how the different classes are supposed to be used.

Code:

#pragma once

#include <type_traits>
#include <algorithm>
#include <exception>
#include <stdexcept>
#include <memory>
#include <utility>
#include <iterator>
#include <array>
#include <vector>

/********************************
This intends to provide a general use multi dimensional view over a
1-D array. Public API classes:

template <class T, int dims, class Allocator = allocator<T> >
class Array: public ArrayBase<T, dims,
        typename allocator_traits<Allocator>::size_type,
        typename allocator_traits<Allocator>::difference_type>
This is a quasi container in the sense that it holds the internal array in
a member vector. But it is only a proxy container because it pretends that
its elements are Row objects
Array can be initialized from a raw array along with its dimensions, and
it will hold a copy of the original data. It supports copy/move/swap semantics
from another Array or copy semantics from any ArrayBase

template <class T, int dims, class size_type = size_t,
class difference_type = ptrdiff_t>
class View : public ArrayBase<T, dims, size_type, difference_type> {
This a a View over an external raw array which is expected to have a lifetime
at least as long as the view. It will also pretends that its elements are Row
objects.
View can be initialized from a raw array and its dimensions, and will only
keep a raw pointer over the original array. It supports copy semantics from
any ArrayBase. Views are swappable, but this only change where each view
points to

template <class T, int dims, class size_type, class difference_type>
class Row : public ArrayBase<T, dims, size_type, difference_type>
This class represents a subarray of an Array, a View or another Row. It can
only be created as an element of its parent. Rows can be copied, or moved
from any ArrayBase. Rows are swappable. Operator & on a row returns an iterator.

template <class T, int dims, class size_type, class difference_type>
class ArrayBase: ElementAccessor<T, size_type, difference_type,
size_type, dims>
ArrayBase is the base class of Array, View and ArrayBase. It contains all
the logic of element access and iteration. Its iterators pretend to be
random access iterators, but are only proxy iterators. That is quite enough 
for for loops with container syntax, and iterator arithmetics, but usage with
standard algorithms (sort, shuffle, etc.) cannot be guaranteed

template <class T, class Allocator = std::allocator<T> >
class ArrayBuilder;
This is only a utility class that knows how to build Array of View objects.
It can save some boiler plate template declaration by declaring the types 
once at builder initialization, mainly when using not default allocators,
size_type or difference_type.
********************************/
namespace DynArray {

    using std::allocator;
    using std::allocator_traits;

    //Forward declarations
    template <class T, int dims, class size_type, class difference_type,
        bool cnst = false, bool rev=false> class Iterator;

    template <class T, int dims, class size_type, class difference_type>
    class Row;

    namespace {
        /* private utility class to allow specialization of element access
        through operator[] or operator *
        */
        template <class T, class size_type, class difference_type,
            class index_type, int dims> class ElementAccessor {
        protected:
            Row<T, dims - 1, size_type, difference_type> getElt(T* arr,
                size_type *sizes, size_type rowsize, index_type i) {
                Row<T, dims - 1, size_type, difference_type> child(
                    arr + rowsize * i,
                    sizes + 1, rowsize / sizes[1]);
                return child;
            }
            const Row<T, dims - 1, size_type, difference_type> getConstElt(T* arr,
                size_type *sizes, size_type rowsize, index_type i) {
                Row<T, dims - 1, size_type, difference_type> child(
                    arr + rowsize * i,
                    sizes + 1, rowsize / sizes[1]);
                return child;
            }
        };
        // specialization for dims == 1: elements are actual T
        template <class T, class size_type, class difference_type,
            class index_type> class ElementAccessor<T, size_type,
            difference_type, index_type, 1> {
        protected:
            T& getElt(T* arr, size_type *sizes, size_type rowsize,
                index_type i) {
                return arr[i];
            }
            const T& getConstElt(T* arr, size_type *sizes,
                size_type rowsize, index_type i) {
                return arr[i];
            }
        };
    }

    // Base: contains the data and types and manages access to elements
    // and sub arrays

    template <class T, int dims, class size_type, class difference_type>
    class ArrayBase: ElementAccessor<T, size_type, difference_type,
        size_type, dims> {
    public:
        using iterator = typename Iterator<T, dims, size_type,
            difference_type, 0, 0>;
        using const_iterator = typename Iterator<T, dims, size_type,
            difference_type, 1, 0>;
        typedef typename std::conditional<dims == 1, T,
            Row<T, dims - 1, size_type, difference_type>>::type value_type;
        typedef typename std::conditional<dims == 1, T&,
            value_type>::type reference;
        // reference is indeed a proxy to real data when dims != 1
        using reverse_iterator = typename Iterator<T, dims, size_type,
            difference_type, 0, 1>;
        using const_reverse_iterator = typename Iterator<T, dims, size_type,
            difference_type, 1, 1>;

    protected:
        T* arr;                      // underlying array for the data
        size_type *sizes;            // array of dimensions of the nD array
        size_type rowsize;           // cached size of a top level row

        // protected ctor called from subclasses
        ArrayBase(T* arr, size_type* sizes, size_type rowsize)
            : arr(arr), sizes(sizes), rowsize(rowsize) {}

        // pseudo-assignment from subclasses
        void load(T* arr, size_type *sizes, size_type rowsize) {
            this->arr = arr;
            this->sizes = sizes;
            this->rowsize = rowsize;
        }

    public:
        virtual ~ArrayBase() = default;

        // access to member
        reference operator[] (size_type i) noexcept {
            return getElt(arr, sizes, rowsize, i);
        }
        const reference operator[] (size_type i) const noexcept {
            return getConstElt(arr, sizes, rowsize, i);
        }
        // access to internal data, arr and sizes and number of dimensions
        size_type size(size_type i) const {
            if (i >= dims) {
                throw std::out_of_range("Illegal dimension");
            }
            return sizes[i];
        }
        size_type size() const noexcept {
            return sizes[0];
        }
        size_type tot_size() const noexcept {
            return sizes[0] * rowsize;
        }
        T* data() noexcept {
            return arr;
        }
        const T* data() const noexcept {
            return arr;
        }
        constexpr int getdims() const noexcept {
            return dims;
        }

        // iterators
        iterator begin() {
            return iterator(arr, sizes, rowsize);
        }
        iterator end() {
            iterator tmp = begin();
            tmp += sizes[0];
            return tmp;
        }
        const_iterator begin() const {
            return cbegin();
        }
        const_iterator end() const {
            return cend();
        }
        const_iterator cbegin() const {
            return const_iterator(arr, sizes, rowsize);
        }
        const_iterator cend() const {
            const_iterator tmp = cbegin();
            tmp += sizes[0];
            return tmp;
        }
        reverse_iterator rbegin() {
            reverse_iterator tmp = rend();
            tmp -= sizes[0];
            return tmp;
        }
        reverse_iterator rend() {
            return reverse_iterator(arr, sizes, rowsize);
        }
        const_reverse_iterator rbegin() const {
            return rcbegin();
        }
        const_reverse_iterator rend() const {
            return rcend();
        }
        const_reverse_iterator rcbegin() const {
            const_reverse_iterator tmp = rend();
            tmp -= sizes[0];
            return tmp;
        }
        const_reverse_iterator rcend() const {
            return const_reverse_iterator(arr, sizes, rowsize);
        }
    };

    // Row represents a sub-array. Handles copying, moving and swapping rows
    // can only be created by an ElementAccessor
    template <class T, int dims, class size_type, class difference_type>
    class Row : public ArrayBase<T, dims, size_type, difference_type> {
    protected:
        using ArrayBase::arr;
        using ArrayBase::sizes;
        using ArrayBase::rowsize;

        Row(T* arr, size_type* sizes, size_type rowsize)
            : ArrayBase<T, dims, size_type, difference_type>(arr,
                sizes, rowsize) {}

    public:
        using base = ArrayBase<T, dims, size_type, difference_type>;

        /* copy/move assignment (construction can only be from an ElementAccessor)
        Programmers must use a view or a reference to point to a row, or an
        Array to get a copy.
        Swap operation is also possible */
        Row& operator = (const base& src) {
            if (tot_size() != src.tot_size()) {
                throw std::logic_error("Wrong sizes");
            }
            for (size_type i = 0; i < tot_size(); i++) {
                arr[i] = src.data()[i];
            }
            return *this;
        }
        Row& operator = (base&& src) {
            if (tot_size() != src.tot_size()) {
                throw std::logic_error("Wrong sizes");
            }
            for (size_type i = 0; i < tot_size(); i++) {
                arr[i] = std::move(src.data()[i]);
            }
            return *this;
        }

        Iterator<T, dims + 1, size_type, difference_type, 0, 0> operator & () {
            return Iterator<T, dims + 1, size_type,difference_type,
                0, 0>(arr, sizes - 1, sizes[0] * rowsize);
        }
        Iterator<T, dims + 1, size_type, difference_type, 1, 0> operator & () const {
            return Iterator<T, dims + 1, size_type, difference_type,
                1, 0>(arr, sizes - 1, sizes[0] * rowsize);
        }
        // 1 argument swap allows the other member to be any ArrayBase
        void swap(ArrayBase& other) {
            using std::swap;

            if (tot_size() != other.tot_size()) {
                throw std::logic_error("Wrong sizes");
            }
            for (size_type i = 0; i < tot_size(); i++) {
                swap(arr[i], other.data()[i]);
            }
        }
        friend class ElementAccessor<T, size_type, difference_type,
            size_type, dims + 1>;
        friend class ElementAccessor<T, size_type, difference_type,
            difference_type, dims + 1>;
    };

    // 2 arguments swap between Rows
    template <class T, int dims, class size_type, class difference_type>
    void swap(Row<T, dims, size_type, difference_type>& first,
        Row<T, dims, size_type, difference_type>& second) {
        first.swap(second);
    }

    namespace {
        /* private auxilliary functions to build a sizes array and
        compute total sizes when given the dimensions */
        template <class size_type, class...U>
        size_type calc_size(size_type *sizes, size_type first,
            U...others) {
            if (sizes != nullptr) *sizes = first;
            return first * calc_size<size_type>(sizes + 1, others...);
        }
        template<class size_type>
        size_type calc_size(size_type *sizes, size_type first) {
            if (sizes != nullptr) *sizes = first;
            return first;
        }
    }

    // View is a top-level nD array over an existing raw array - no ownership
    template <class T, int dims, class size_type = size_t,
        class difference_type = ptrdiff_t>
    class View : public ArrayBase<T, dims, size_type, difference_type> {
    public:
        using base = ArrayBase<T, dims, size_type, difference_type>;
    private:
        using ArrayBase::arr;
        using ArrayBase::sizes;
        using ArrayBase::rowsize;

        // private array to hold the actual dimensions
        // constraint: sizes shall point to _sizes
        size_type _sizes[dims];

    public:
        /* public ctors, assignment operators and swap.
        Only copy semantics, because assignment only changes where the
        view points to, not the underlying data */
        template <class...U,
            typename = std::enable_if<dims == sizeof...(U)>::type>
        View(T* arr, U...sz): base(arr, _sizes, 0) {
            size_t tot = calc_size<size_type>(sizes, sz...);
            rowsize = tot / sizes[0];
        }
        View(const base& other) :
            ArrayBase<T, dims, size_type, difference_type>(other) {
            std::copy(sizes, sizes + dims, _sizes);
            sizes = _sizes;
        }
        View(const View& other) :
            ArrayBase<T, dims, size_type, difference_type>(other) {
            std::copy(sizes, sizes + dims, _sizes);
            sizes = _sizes;
        }
        View& operator = (const base& other) {
            ArrayBase.operator = (other);
            std::copy(sizes, sizes + dims, _sizes);
            sizes = _sizes;
        }
        void swap(View& other) {
            using std::swap;

            swap(_sizes, other._sizes);
            T *tmparr = arr;
            size_type tmprs = rowsize;
            ArrayBase::operator = (other);
            other.load(tmparr, sizes, tmprs);
            sizes = _sizes;
        }
    };

    template <class T, int dims, class size_type = size_t,
        class difference_type = ptrdiff_t>
        void swap(View<T, dims, size_type, difference_type>& first,
            View<T, dims, size_type, difference_type>& second) {
        first.swap(second);
    }

    // Full array, holds (a copy of) the underlying data
    template <class T, int dims, class Allocator = allocator<T> >
    class Array : public ArrayBase<T, dims,
        typename allocator_traits<Allocator>::size_type,
        typename allocator_traits<Allocator>::difference_type> {

    public:
        using size_type = typename allocator_traits<Allocator>::size_type;
        using difference_type =
            typename allocator_traits<Allocator>::difference_type;

    private:
        using base = ArrayBase<T, dims,
            typename allocator_traits<Allocator>::size_type,
            typename allocator_traits<Allocator>::difference_type>;
        using ArrayBase::arr;
        using ArrayBase::sizes;
        using ArrayBase::rowsize;

        Allocator alloc;      // internal allocator

        size_type _sizes[dims];
        std::vector<T, Allocator> _arr;

        template<class...U>
        void init(size_type first, U... others) {
            sizes = _sizes;
            size_t tot = calc_size<size_type>(sizes, first, others...);
            rowsize = tot / sizes[0];
            if (arr == nullptr) {
                _arr.assign(tot, T());
            }
            else {
                _arr.assign(arr, arr + tot);
            }
            this->arr = _arr.data();
        }
    public:

        template<class...U,
            typename = std::enable_if<sizeof...(U)+1 == dims>::type>
            Array(T* arr, Allocator alloc, size_type first, U... others)
            : base(arr, nullptr, 0), alloc(alloc), _arr(this->alloc) {
            init(first, others...);
        }
        template<class...U,
            typename = std::enable_if<sizeof...(U)+1 == dims>::type>
            Array(T* arr, size_type first, U... others)
            : base(arr, nullptr, 0), _arr(this->alloc) {
            init(first, others...);
        }
        template<class...U,
            typename = std::enable_if<sizeof...(U)+1 == dims>::type>
            Array(Allocator alloc, size_type first, U... others)
            : base(nullptr, nullptr, 0), alloc(alloc), _arr(this->alloc) {
            init(first, others...);
        }
        template<class...U,
            typename = std::enable_if<sizeof...(U)+1 == dims>::type>
            Array(size_type first, U... others)
            : base(nullptr, nullptr, 0), _arr(this->alloc) {
            init(first, others...);
        }
        // copy/move ctors and assignment from another array

        // TODO: implement move semantics from an ArrayBase
        Array(const Array& other)
            : ArrayBase(other), alloc(other.alloc),
            _arr(other._arr) {
            std::copy(sizes, sizes + dims, _sizes);
            arr = _arr.data();
            sizes = _sizes;
        }
        Array(Array&& other) : ArrayBase(other), alloc(other.alloc),
            _arr(std::move(other._arr)) {
            std::copy(sizes, sizes + dims, _sizes);
            arr = _arr.data();
            sizes = _sizes;
        }
        Array(const ArrayBase& other) : ArrayBase(other), alloc(),
            _arr(arr, arr + rowsize * sizes[0]) {
            std::copy(sizes, sizes + dims, _sizes);
            arr = _arr.data();
            sizes = _sizes;
        }
        Array& operator = (const Array& other) {
            load(other);
            if (std::allocator_traits(
                Allocator)::propagate_on_container_copy_assignment) {
                alloc = other.alloc;
            }
            std::copy(sizes, sizes + dims, _sizes);
            _arr.assign(other._arr);
            arr = _arr.data();
            sizes = _sizes;
            return *this;
        }
        Array& operator = (Array&& other) {
            ArrayBase::operator = (other);
            if (std::allocator_traits<
                Allocator>::propagate_on_container_move_assignment::value) {
                alloc = other.alloc;
            }
            std::copy(sizes, sizes + dims, _sizes);
            _arr = std::move(other._arr);
            arr = _arr.data();
            sizes = _sizes;
            return *this;
        }
        Array& operator = (const ArrayBase& other) {
            ArrayBase::operator = (other);
            std::copy(sizes, sizes + dims, _sizes);
            _arr.assign(arr, arr + sizes[0] * rowsize);
            arr = _arr.data();
            sizes = _sizes;
            return *this;
        }
    };

    /* syntactic sugar to help template deduction and avoid some (re-)typing
    mainly usefull for non default allocators */
    template <class T, class Allocator = std::allocator<T> >
    class ArrayBuilder {
    public:
        using size_type = typename allocator_traits<Allocator>::size_type;
        using difference_type
            = typename allocator_traits<Allocator>::difference_type;
    private:
        Allocator alloc;

    public:
        ArrayBuilder(const Allocator& alloc = Allocator()) : alloc(alloc) {}

        template <class ...U, int dims = sizeof...(U)+1>
        View<T, dims, size_type, difference_type> dynUseArray(T* arr,
            size_type first, U...others) {
            return View<T, dims, size_type, difference_type>(arr, first,
                others...);
        }
        template <class ...U, int dims = sizeof...(U)+1>
        Array<T, dims, Allocator> dynCopyArray(T* arr,
            size_type first, U...others) {
            return Array<T, dims, Allocator>(arr, alloc,
                first, others...);
        }
        template <class ...U, int dims = sizeof...(U)+1>
        Array<T, dims, Allocator> dynBuildArray(size_type first, U...others) {
            return Array<T, dims, Allocator>(alloc, first, others...);
        }
    };

    // iterator if cnst == 0 or const_iterator if cnst == 1, U is the value_type
    template <class T, int dims, class size_type,
        class difference_type, bool cnst, bool rev>
    class Iterator: public ElementAccessor<T, size_type, difference_type,
    difference_type, dims>{

    public:
        using value_type = typename std::conditional<cnst,
            typename std::conditional<dims == 1, const T,
            const Row<T, dims-1, size_type, difference_type>>::type,
            typename std::conditional<dims == 1, T, Row<T, dims - 1, size_type,
            difference_type>>::type>::type;
        using reference = typename std::conditional < dims == 1,
            value_type&, value_type>::type;
        using iterator_category = typename std::random_access_iterator_tag;

    private:

        struct Proxy {
            value_type elt;

            Proxy(value_type&& elt) : elt(elt) {}

            value_type* operator ->() {
                return std::addressof(elt);
            }
        };
        T* arr;
        size_type *sizes;
        size_type rowsize;

        Iterator(T* arr, size_type *sizes, size_type rowsize) :
            arr(arr), sizes(sizes), rowsize(rowsize) {}

        template<bool x=false>
        reference getXElt(difference_type i) {
            return getElt(arr - rowsize * (rev ? 1 : 0), sizes, rowsize, i);
        }
        template<>
        reference getXElt<true>(difference_type i) {
            return getConstElt(arr - rowsize * (rev ? 1 : 0), sizes, rowsize, i);
        }
        void add(difference_type i) {
            arr += (rev ? -i : i) * rowsize ;
        }

        using iterator = Iterator<T, dims, size_type, difference_type, cnst, rev>;

    public:
        using pointer = Proxy;

        // a default ctor (to mimic standard iterators)
        Iterator(): arr(nullptr), sizes(nullptr), rowsize(0) {}

        //convert an (non const) iterator to a const_iterator
        template <class X = T, typename = std::enable_if<cnst == 1>::type>
        Iterator(const Iterator<T, dims, size_type, difference_type,
            1 - cnst, rev>& other)
            : arr(other.arr), sizes(other.sizes), rowsize(other.rowsize) {}

        // all operations of an iterator
        reference operator * () {
            return getXElt(0) ;
        }
        pointer operator -> () {
            return Proxy(getXElt(0));
        }
        const reference operator * () const {
            return getConstElt(arr - rowsize * (rev ? 1 : 0), sizes, rowsize, 0);
        }
        const pointer operator -> () const {
            return Proxy(getXElt(0));
        }
        iterator& operator ++() {
            this->add(1);
            return *this;
        }
        iterator& operator --() {
            this->add(-1);
            return *this;
        }
        iterator operator ++(int) {
            iterator tmp = *this;
            this->add(1);
            return tmp;
        }
        iterator operator --(int) {
            iterator tmp = *this;
            this->add(-1);
            return tmp;
        }
        iterator& operator += (difference_type i) {
            this->add(i);
            return *this;
        }
        iterator operator + (difference_type i) {
            iterator tmp = *this;
            tmp.add(i);
            return tmp;
        }
        iterator operator -= (difference_type i) {
            return operator += (-i);
        }
        iterator operator - (difference_type i) {
            return operator + (-i);
        }

        value_type operator [] (difference_type i) {
            return *(*this + i);
        }
        const value_type operator [] (difference_type i) const {
            return *(*this + i);
        }

        // comparisons are allowed between const and non const iterators
        template <bool c>
        bool operator ==(const Iterator<T, dims, size_type,
            difference_type, c, rev>& other) const {
            return (arr == other.arr) && (sizes == other.sizes)
                && (rowsize == other.rowsize);
        }
        template <bool c>
        bool operator != (const Iterator<T, dims, size_type,
            difference_type, c, rev>& other) const {
            return !operator ==(other);
        }
        template <bool c>
        bool operator <(const Iterator<T, dims, size_type,
            difference_type, rev, c>& other) const {
            return arr < other.arr;
        }
        template <bool c>
        bool operator >(const Iterator<T, dims, size_type,
            difference_type, c, rev>& other) const {
            return arr > other.arr;
        }
        template <bool c>
        bool operator <=(const Iterator<T, dims, size_type,
            difference_type, c, rev>& other) const {
            return !operator >(other);
        }
        template <bool c>
        bool operator >=(const Iterator<T, dims, size_type,
            difference_type, c, rev>& other) const {
            return !operator <(other);
        }

        friend class ArrayBase<T, dims, size_type, difference_type>;
        friend class Iterator<T, dims, size_type,
            difference_type, !cnst, rev>;
        friend class Row<T, dims - 1, size_type, difference_type>;
    };
}

For reference here are the unit test classes for MSCV 2017 test framework

#include "stdafx.h"
#include "CppUnitTest.h"
#include "../mdynarray/mdynarray.h"

using namespace Microsoft::VisualStudio::CppUnitTestFramework;

namespace UnitTest1
{       
    using namespace DynArray;
    TEST_CLASS(UnitTest1)
    {
    public:

        /* creation of a view shall point to original raw array */
        TEST_METHOD(useArr)
        {
            ArrayBuilder<int> builder;
            int arr[60];
            int l = 0;
            for (int& i : arr) {
                i = l++;
            }
            auto dynarray = builder.dynUseArray(arr, 3, 4, 5);
            l = 0;
            for (int i = 0; i < 3; i++) {
                for (int j = 0; j < 4; j++) {
                    for (int k = 0; k < 5; k++) {
                        Assert::AreSame(dynarray[i][j][k], arr[l++]);
                    }
                }
            }
        }
        /* Assignment of a row from a second view */
        TEST_METHOD(rowAssign) {
            int arr[60];
            int l = 0;
            for (int& i : arr) {
                i = l++;
            }
            View<int, 3> array {arr, 3, 4, 5};

            int arr2[20];
            l = 64;
            for (int&i : arr) {
                i = l++;
            }
            View<int, 2> array2{ arr2, 4, 5 };

            array[1] = array2;
            /* controls that:
            - values have been copied
            - indices still point to original raw array
            */
            for (int i = 0; i < 4; i++) {
                for (int j = 0; j < 5; j++) {
                    Assert::AreNotSame(array[1][i][j], array2[i][j]);
                    Assert::AreEqual(array[1][i][j], array2[i][j]);
                    Assert::AreSame(arr[20 + 5 * i + j], array[1][i][j]);
                }
            }
        }
        // swap Rows
        TEST_METHOD(rowSwap) {
            int arr[60];
            int l = 0;
            for (int& i : arr) {
                i = l++;
            }
            View<int, 3> array {arr, 3, 4, 5};

            swap(array[0], array[2]);
            l = 40;
            int k = 0;
            for (int i = 0; i < 4; i++) {
                for (int j = 0; j < 5; j++) {
                    Assert::AreSame(arr[k++], array[0][i][j]);
                    Assert::AreEqual(l++, array[0][i][j]);
                }
            }
        }
        //Assignement of a view from a Row or another View
        TEST_METHOD(viewAssign) {
            int arr[60];
            int l = 0;
            for (int& i : arr) {
                i = l++;
            }
            View<int, 3> array {arr, 3, 4, 5};

            View<int, 2> arr0{ array[0] };
            l = 0;
            for (int i = 0; i < 4; i++) {
                for (int j = 0; j < 5; j++) {
                    Assert::AreSame(arr[l++], arr0[i][j]);
                }
            }
            View<int, 2> arr2 = array[2];
            arr0 = arr2;
            l = 40;
            for (int i = 0; i < 4; i++) {
                for (int j = 0; j < 5; j++) {
                    Assert::AreSame(arr[l++], arr0[i][j]);
                }
            }
        }
        // swap views
        TEST_METHOD(viewSwap) {
            int arr[60];
            int l = 0;
            for (int& i : arr) {
                i = l++;
            }
            View<int, 3> array {arr, 3, 4, 5};

            View<int, 2> arr0{ array[0] };
            View<int, 2> arr2 = array[2];
            swap(arr0, arr2);
            l = 40;
            int k = 0;
            for (int i = 0; i < 4; i++) {
                for (int j = 0; j < 5; j++) {
                    Assert::AreSame(arr[l++], arr0[i][j]);
                    Assert::AreSame(arr[k++], arr2[i][j]);
                }
            }
        }

        TEST_METHOD(copyArr)
        {
            ArrayBuilder<int> builder;
            int arr[60];
            int l = 0;
            for (int& i : arr) {
                i = l++;
            }
            auto dynarray = builder.dynCopyArray(arr, 3, 4, 5);
            l = 0;
            for (int i = 0; i < 3; i++) {
                for (int j = 0; j < 4; j++) {
                    for (int k = 0; k < 5; k++) {
                        Assert::AreEqual(dynarray[i][j][k], arr[l]);
                        Assert::AreNotSame(dynarray[i][j][k], arr[l]);
                        l++;
                    }
                }
            }
        }

        TEST_METHOD(buildArr)
        {
            ArrayBuilder<int> builder;
            auto dynarray = builder.dynBuildArray(3, 4, 5);
            for (int i = 0; i < 3; i++) {
                for (int j = 0; j < 4; j++) {
                    for (int k = 0; k < 5; k++) {
                        Assert::AreEqual(dynarray[i][j][k], 0);
                    }
                }
            }
        }

        TEST_METHOD(copyCtor)
        {
            ArrayBuilder<int> builder;
            int arr[60];
            int l = 0;
            for (int& i : arr) {
                i = l++;
            }
            auto dynarray = builder.dynUseArray(arr, 3, 4, 5);
            Array<int, 3> dyn2{ dynarray };
            l = 0;
            for (int i = 0; i < 3; i++) {
                for (int j = 0; j < 4; j++) {
                    for (int k = 0; k < 5; k++) {
                        Assert::AreEqual(dyn2[i][j][k], arr[l]);
                        Assert::AreNotSame(dyn2[i][j][k], arr[l]);
                        l++;
                    }
                }
            }
        }

        TEST_METHOD(moveCtor)
        {
            ArrayBuilder<int> builder;
            int arr[60];
            int l = 0;
            for (int& i : arr) {
                i = l++;
            }
            auto dynarray = builder.dynCopyArray(arr, 3, 4, 5);
            int *ix = dynarray.data();
            auto dyn2 = std::move(dynarray);
            Assert::AreEqual(ix, dyn2.data());
            l = 0;
            for (int i = 0; i < 3; i++) {
                for (int j = 0; j < 4; j++) {
                    for (int k = 0; k < 5; k++) {
                        Assert::AreEqual(dyn2[i][j][k], arr[l]);
                        l++;
                    }
                }
            }
        }

        TEST_METHOD(copyAssign)
        {
            ArrayBuilder<int> builder;
            int arr[60];
            int l = 0;
            for (int& i : arr) {
                i = l++;
            }
            auto dynarray = builder.dynUseArray(arr, 3, 4, 5);
            auto dyn2 = builder.dynBuildArray(3, 4, 5);
            dyn2 = dynarray;
            l = 0;
            for (int i = 0; i < 3; i++) {
                for (int j = 0; j < 4; j++) {
                    for (int k = 0; k < 5; k++) {
                        Assert::AreEqual(dyn2[i][j][k], arr[l]);
                        Assert::AreNotSame(dyn2[i][j][k], arr[l]);
                        l++;
                    }
                }
            }
        }

        TEST_METHOD(moveAssign)
        {
            ArrayBuilder<int> builder;
            int arr[60];
            int l = 0;
            for (int& i : arr) {
                i = l++;
            }
            auto dynarray = builder.dynCopyArray(arr, 3, 4, 5);
            int *ix = dynarray.data();
            auto dyn2 = builder.dynBuildArray(3, 4, 5);
            dyn2 = std::move(dynarray);
            l = 0;
            for (int i = 0; i < 3; i++) {
                for (int j = 0; j < 4; j++) {
                    for (int k = 0; k < 5; k++) {
                        Assert::AreEqual(dyn2[i][j][k], arr[l]);
                        l++;
                    }
                }
            }
            Assert::AreEqual(ix, dyn2.data());
        }

        TEST_METHOD(nonConstIter)
        {
            ArrayBuilder<int> builder;
            int arr[60];
            int l = 0;
            for (int& i : arr) {
                i = l++;
            }
            auto dynarray = builder.dynUseArray(arr, 3, 4, 5);
            l = 0;
            for (auto& it1 : dynarray) {
                for (auto& it2 : it1) {
                    for (auto& it3 : it2) {
                        Assert::AreSame(it3, arr[l]);
                        l++;
                        it3 = l;             // control it is not const...
                    }
                }
            }
        }

        TEST_METHOD(constIter)
        {
            ArrayBuilder<int> builder;
            int arr[60];
            int l = 0;
            for (int& i : arr) {
                i = l++;
            }
            auto dynarray = builder.dynUseArray(arr, 3, 4, 5);
            l = 0;
            for (auto it1 = dynarray.cbegin(); it1 != dynarray.cend(); it1++) {
                for (auto it2 = it1->cbegin(); it2 != it1->cend(); it2++) {
                    for (auto it3 = it2->cbegin(); it3 != it2->cend(); it3++) {
                        Assert::AreSame(*it3, arr[l]);
                        l++;
                        // *it3 = l;           // does not compile
                    }
                }
            }
        }

        TEST_METHOD(convConstIterator)
        {
            ArrayBuilder<int> builder;
            int arr[60];
            int l = 0;
            for (int& i : arr) {
                i = l++;
            }
            auto dynarray = builder.dynUseArray(arr, 3, 4, 5);

            auto it = dynarray.begin();
            Array<int, 3>::const_iterator cit = it;

            //it = (MDynArray<int, 3>::iterator) cit;  // does not compile

            it += 1;
            cit += 1;
            Assert::IsTrue(it > dynarray.begin());
            Assert::IsTrue(it == cit);
            Assert::IsTrue(cit == it);
        }

        TEST_METHOD(revIterator)
        {
            ArrayBuilder<int> builder;
            int arr[60];
            int l = 0;
            for (int& i : arr) {
                i = l++;
            }
            auto dynarray = builder.dynUseArray(arr, 3, 4, 5);
            l = 0;

            for (auto it1 = dynarray.rbegin(); it1 != dynarray.rend(); it1++) {
                for (auto it2 = it1->rbegin(); it2 != it1->rend(); it2++) {
                    for (auto it3 = it2->rbegin(); it3 != it2->rend(); it3++) {
                        Assert::AreSame(*it3, arr[59 - l]);
                        l++;
                        *it3 = l;           // control non constness
                    }
                }
            }
        }

        TEST_METHOD(rowToIter)
        {
            ArrayBuilder<int> builder;
            int arr[60];
            int l = 0;
            for (int& i : arr) {
                i = l++;
            }
            auto dynarray = builder.dynUseArray(arr, 3, 4, 5);
            l = 0;

            auto row = dynarray[1];
            auto it = &row - 1;
            Assert::AreSame(arr[0], it[0][0][0]);

        }

    };
}

I admit it is a rather large piece of code...

\$\endgroup\$
2
\$\begingroup\$

I found a number of compilation issues using GCC 8 (with -std=c++2a). I'm guessing you're using an older or less-conformant compiler. Here's what I had to change.


The typename keyword isn't wanted here (where it knows that Iterator<> is a type):

    using iterator = typename Iterator<T, dims, size_type,
        difference_type, 0, 0>;

Removing the unnecessary keyword makes this bit happy.


Next, it complains that Row::ArrayBase is used without its template parameters here:

    using ArrayBase::arr;
    using ArrayBase::sizes;
    using ArrayBase::rowsize;

We can either write the class name in full, or move the definition of base a bit earlier, and use that (I'll choose the latter).

We also need using ArrayBase::tot_size; here, to access it without qualification.

There's a similar issue in View and in Array, but here, base is already declared early enough to use in place of ArrayBase.


We need to add typename here (perhaps we can use one of the rescued ones from the beginning of this review?):

    template <class...U,
        typename = typename std::enable_if<dims == sizeof...(U)>::type>
                 //^^^^^^^^\\

A simple typo here:

        if (std::allocator_traits(
            Allocator)::propagate_on_container_copy_assignment::value

Should be std::allocator_traits<Allocator>propagate_on_container_copy_assignment::value, of course (I'm surprised this even compiles on your system!)


The specialization of getXElt() is illegally placed within the Iterator definition; it needs to be external. It's really hard to get the nested template syntax right there (and even harder to read and understand), so I'd prefer to combine the implementations using if constexpr:

    template<bool x=false>
    reference getXElt(difference_type i) {
        if constexpr(x) {
            return getConstElt(arr - rowsize * (rev ? 1 : 0), sizes, rowsize, i);
        } else {
            return getElt(arr - rowsize * (rev ? 1 : 0), sizes, rowsize, i);
        }
    }

If that's not available to you, you could use std::enable_if as for other methods in this class.


ArrayBase needs to use this->getElt to access its dependent base class:

    reference operator[](size_type i) noexcept {
        return this->getElt(arr, sizes, rowsize, i);
    }
    const reference operator[](size_type i) const noexcept {
        return this->getConstElt(arr, sizes, rowsize, i);
    }

The constIter test wouldn't compile because for (auto& it1 : dynarray) attempts to bind non-const lvalue reference of type DynArray::Row<int, 2, long unsigned int, long int>& to an rvalue of type DynArray::Iterator<int, 3, long unsigned int, long int, false, false>::reference {aka DynArray::Row<int, 2, long unsigned int, long int>}.

The rowSwap test won't compile for a similar reason (attempt to bind an lvalue reference to rvalue in swap())


Here's what I ended up with when I'd made the minimal changes to compile, and converted to GTest (since your choice of test framework isn't available in Debian):

#include <type_traits>
#include <algorithm>
#include <exception>
#include <stdexcept>
#include <memory>
#include <utility>
#include <iterator>
#include <array>
#include <vector>

/********************************
This intends to provide a general use multi dimensional view over a
1-D array. Public API classes:

template <class T, int dims, class Allocator = allocator<T> >
class Array: public ArrayBase<T, dims,
        typename allocator_traits<Allocator>::size_type,
        typename allocator_traits<Allocator>::difference_type>
This is a quasi container in the sense that it holds the internal array in
a member vector. But it is only a proxy container because it pretends that
its elements are Row objects
Array can be initialized from a raw array along with its dimensions, and
it will hold a copy of the original data. It supports copy/move/swap semantics
from another Array or copy semantics from any ArrayBase

template <class T, int dims, class size_type = size_t,
class difference_type = ptrdiff_t>
class View : public ArrayBase<T, dims, size_type, difference_type> {
This a a View over an external raw array which is expected to have a lifetime
at least as long as the view. It will also pretends that its elements are Row
objects.
View can be initialized from a raw array and its dimensions, and will only
keep a raw pointer over the original array. It supports copy semantics from
any ArrayBase. Views are swappable, but this only change where each view
points to

template <class T, int dims, class size_type, class difference_type>
class Row : public ArrayBase<T, dims, size_type, difference_type>
This class represents a subarray of an Array, a View or another Row. It can
only be created as an element of its parent. Rows can be copied, or moved
from any ArrayBase. Rows are swappable. Operator & on a row returns an iterator.

template <class T, int dims, class size_type, class difference_type>
class ArrayBase: ElementAccessor<T, size_type, difference_type,
size_type, dims>
ArrayBase is the base class of Array, View and ArrayBase. It contains all
the logic of element access and iteration. Its iterators pretend to be
random access iterators, but are only proxy iterators. That is quite enough 
for for loops with container syntax, and iterator arithmetics, but usage with
standard algorithms (sort, shuffle, etc.) cannot be guaranteed

template <class T, class Allocator = std::allocator<T> >
class ArrayBuilder;
This is only a utility class that knows how to build Array of View objects.
It can save some boiler plate template declaration by declaring the types 
once at builder initialization, mainly when using not default allocators,
size_type or difference_type.
********************************/
namespace DynArray {

    using std::allocator;
    using std::allocator_traits;

    //Forward declarations
    template <class T, int dims, class size_type, class difference_type,
        bool cnst = false, bool rev=false> class Iterator;

    template <class T, int dims, class size_type, class difference_type>
    class Row;

    namespace {
        /* private utility class to allow specialization of element access
        through operator[] or operator *
        */
        template <class T, class size_type, class difference_type,
            class index_type, int dims> class ElementAccessor {
        protected:
            Row<T, dims - 1, size_type, difference_type> getElt(T* arr,
                size_type *sizes, size_type rowsize, index_type i) {
                Row<T, dims - 1, size_type, difference_type> child(
                    arr + rowsize * i,
                    sizes + 1, rowsize / sizes[1]);
                return child;
            }
            const Row<T, dims - 1, size_type, difference_type> getConstElt(T* arr,
                size_type *sizes, size_type rowsize, index_type i) {
                Row<T, dims - 1, size_type, difference_type> child(
                    arr + rowsize * i,
                    sizes + 1, rowsize / sizes[1]);
                return child;
            }
        };
        // specialization for dims == 1: elements are actual T
        template <class T, class size_type, class difference_type,
            class index_type> class ElementAccessor<T, size_type,
            difference_type, index_type, 1> {
        protected:
            T& getElt(T* arr, size_type *, size_type,
                index_type i) {
                return arr[i];
            }
            const T& getConstElt(T* arr, size_type *,
                size_type, index_type i) {
                return arr[i];
            }
        };
    }

    // Base: contains the data and types and manages access to elements
    // and sub arrays

    template <class T, int dims, class size_type, class difference_type>
    class ArrayBase: ElementAccessor<T, size_type, difference_type,
        size_type, dims> {
    public:
        using iterator = Iterator<T, dims, size_type,
            difference_type, 0, 0>;
        using const_iterator = Iterator<T, dims, size_type,
            difference_type, 1, 0>;
        typedef typename std::conditional<dims == 1, T,
            Row<T, dims - 1, size_type, difference_type>>::type value_type;
        typedef typename std::conditional<dims == 1, T&,
            value_type>::type reference;
        // reference is indeed a proxy to real data when dims != 1
        using reverse_iterator = Iterator<T, dims, size_type,
            difference_type, 0, 1>;
        using const_reverse_iterator = Iterator<T, dims, size_type,
            difference_type, 1, 1>;

    protected:
        T* arr;                      // underlying array for the data
        size_type *sizes;            // array of dimensions of the nD array
        size_type rowsize;           // cached size of a top level row

        // protected ctor called from subclasses
        ArrayBase(T* arr, size_type* sizes, size_type rowsize)
            : arr(arr), sizes(sizes), rowsize(rowsize) {}

        // pseudo-assignment from subclasses
        void load(T* arr, size_type *sizes, size_type rowsize) {
            this->arr = arr;
            this->sizes = sizes;
            this->rowsize = rowsize;
        }

    public:
        virtual ~ArrayBase() = default;

        // access to member
        reference operator[](size_type i) noexcept {
            return this->getElt(arr, sizes, rowsize, i);
        }
        const reference operator[](size_type i) const noexcept {
            return this->getConstElt(arr, sizes, rowsize, i);
        }
        // access to internal data, arr and sizes and number of dimensions
        size_type size(size_type i) const {
            if (i >= dims) {
                throw std::out_of_range("Illegal dimension");
            }
            return sizes[i];
        }
        size_type size() const noexcept {
            return sizes[0];
        }
        size_type tot_size() const noexcept {
            return sizes[0] * rowsize;
        }
        T* data() noexcept {
            return arr;
        }
        const T* data() const noexcept {
            return arr;
        }
        constexpr int getdims() const noexcept {
            return dims;
        }

        // iterators
        iterator begin() {
            return iterator(arr, sizes, rowsize);
        }
        iterator end() {
            iterator tmp = begin();
            tmp += sizes[0];
            return tmp;
        }
        const_iterator begin() const {
            return cbegin();
        }
        const_iterator end() const {
            return cend();
        }
        const_iterator cbegin() const {
            return const_iterator(arr, sizes, rowsize);
        }
        const_iterator cend() const {
            const_iterator tmp = cbegin();
            tmp += sizes[0];
            return tmp;
        }
        reverse_iterator rbegin() {
            reverse_iterator tmp = rend();
            tmp -= sizes[0];
            return tmp;
        }
        reverse_iterator rend() {
            return reverse_iterator(arr, sizes, rowsize);
        }
        const_reverse_iterator rbegin() const {
            return rcbegin();
        }
        const_reverse_iterator rend() const {
            return rcend();
        }
        const_reverse_iterator rcbegin() const {
            const_reverse_iterator tmp = rend();
            tmp -= sizes[0];
            return tmp;
        }
        const_reverse_iterator rcend() const {
            return const_reverse_iterator(arr, sizes, rowsize);
        }
    };

    // Row represents a sub-array. Handles copying, moving and swapping rows
    // can only be created by an ElementAccessor
    template <class T, int dims, class size_type, class difference_type>
    class Row : public ArrayBase<T, dims, size_type, difference_type> {
    public:
        using base = ArrayBase<T, dims, size_type, difference_type>;

    protected:
        using base::arr;
        using base::sizes;
        using base::rowsize;
        using base::tot_size;

        Row(T* arr, size_type* sizes, size_type rowsize)
            : base(arr, sizes, rowsize) {}

    public:
        /* copy/move assignment (construction can only be from an ElementAccessor)
        Programmers must use a view or a reference to point to a row, or an
        Array to get a copy.
        Swap operation is also possible */
        Row& operator=(const base& src) {
            if (tot_size() != src.tot_size()) {
                throw std::logic_error("Wrong sizes");
            }
            for (size_type i = 0; i < tot_size(); i++) {
                arr[i] = src.data()[i];
            }
            return *this;
        }
        Row& operator=(base&& src) {
            if (tot_size() != src.tot_size()) {
                throw std::logic_error("Wrong sizes");
            }
            for (size_type i = 0; i < tot_size(); i++) {
                arr[i] = std::move(src.data()[i]);
            }
            return *this;
        }

        Iterator<T, dims + 1, size_type, difference_type, 0, 0> operator&() {
            return Iterator<T, dims + 1, size_type,difference_type,
                0, 0>(arr, sizes - 1, sizes[0] * rowsize);
        }
        Iterator<T, dims + 1, size_type, difference_type, 1, 0> operator&() const {
            return Iterator<T, dims + 1, size_type, difference_type,
                1, 0>(arr, sizes - 1, sizes[0] * rowsize);
        }
        // 1 argument swap allows the other member to be any ArrayBase
        void swap(base& other) {
            if (tot_size() != other.tot_size()) {
                throw std::logic_error("Wrong sizes");
            }
            for (size_type i = 0; i < tot_size(); i++) {
                swap(arr[i], other.data()[i]);
            }
        }
        friend class ElementAccessor<T, size_type, difference_type,
            size_type, dims + 1>;
        friend class ElementAccessor<T, size_type, difference_type,
            difference_type, dims + 1>;
    };

    // 2 arguments swap between Rows
    template <class T, int dims, class size_type, class difference_type>
    void swap(Row<T, dims, size_type, difference_type>& first,
        Row<T, dims, size_type, difference_type>& second) {
        first.swap(second);
    }

    namespace {
        template <class size_type, class...U>
        size_type calc_size(size_type *dest, U...sizes)
        {
            if (dest) {
                ((*dest++ = sizes) , ...);
            }
            return (sizes * ...);
        }

    }

    // View is a top-level nD array over an existing raw array - no ownership
    template <class T, int dims, class size_type = size_t,
        class difference_type = ptrdiff_t>
    class View : public ArrayBase<T, dims, size_type, difference_type> {
    public:
        using base = ArrayBase<T, dims, size_type, difference_type>;
    private:
        using base::arr;
        using base::sizes;
        using base::rowsize;

        // private array to hold the actual dimensions
        // constraint: sizes shall point to _sizes
        size_type _sizes[dims];

    public:
        /* public ctors, assignment operators and swap.
        Only copy semantics, because assignment only changes where the
        view points to, not the underlying data */
        template <class...U,
            typename = typename std::enable_if<dims == sizeof...(U)>::type>
        View(T* arr, U...sz): base(arr, _sizes, 0) {
            size_t tot = calc_size<size_type>(sizes, sz...);
            rowsize = tot / sizes[0];
        }
        View(const base& other) :
            ArrayBase<T, dims, size_type, difference_type>(other) {
            std::copy(sizes, sizes + dims, _sizes);
            sizes = _sizes;
        }
        View(const View& other) :
            ArrayBase<T, dims, size_type, difference_type>(other) {
            std::copy(sizes, sizes + dims, _sizes);
            sizes = _sizes;
        }
        View& operator=(const base& other) {
            base::operator=(other);
            std::copy(sizes, sizes + dims, _sizes);
            sizes = _sizes;
        }
        void swap(View& other) {
            using std::swap;

            swap(_sizes, other._sizes);
            T *tmparr = arr;
            size_type tmprs = rowsize;
            base::operator=(other);
            other.load(tmparr, sizes, tmprs);
            sizes = _sizes;
        }
    };

    template <class T, int dims, class size_type = size_t,
        class difference_type = ptrdiff_t>
        void swap(View<T, dims, size_type, difference_type>& first,
            View<T, dims, size_type, difference_type>& second) {
        first.swap(second);
    }

    // Full array, holds (a copy of) the underlying data
    template <class T, int dims, class Allocator = allocator<T> >
    class Array : public ArrayBase<T, dims,
        typename allocator_traits<Allocator>::size_type,
        typename allocator_traits<Allocator>::difference_type> {

    public:
        using size_type = typename allocator_traits<Allocator>::size_type;
        using difference_type =
            typename allocator_traits<Allocator>::difference_type;

    private:
        using base = ArrayBase<T, dims,
            typename allocator_traits<Allocator>::size_type,
            typename allocator_traits<Allocator>::difference_type>;
        using base::arr;
        using base::sizes;
        using base::rowsize;

        Allocator alloc;      // internal allocator

        size_type _sizes[dims];
        std::vector<T, Allocator> _arr;

        template<class...U>
        void init(size_type first, U... others) {
            base::sizes = _sizes;
            size_t tot = calc_size<size_type>(sizes, first, others...);
            base::rowsize = tot / sizes[0];
            if (arr == nullptr) {
                _arr.assign(tot, T());
            }
            else {
                _arr.assign(arr, arr + tot);
            }
            this->arr = _arr.data();
        }
    public:

        template<class...U,
            typename = typename std::enable_if<sizeof...(U)+1 == dims>::type>
            Array(T* arr, Allocator alloc, size_type first, U... others)
            : base(arr, nullptr, 0), alloc(alloc), _arr(this->alloc) {
            init(first, others...);
        }
        template<class...U,
            typename = typename std::enable_if<sizeof...(U)+1 == dims>::type>
            Array(T* arr, size_type first, U... others)
            : base(arr, nullptr, 0), _arr(this->alloc) {
            init(first, others...);
        }
        template<class...U,
            typename = typename std::enable_if<sizeof...(U)+1 == dims>::type>
            Array(Allocator alloc, size_type first, U... others)
            : base(nullptr, nullptr, 0), alloc(alloc), _arr(this->alloc) {
            init(first, others...);
        }
        template<class...U,
            typename = typename std::enable_if<sizeof...(U)+1 == dims>::type>
            Array(size_type first, U... others)
            : base(nullptr, nullptr, 0), _arr(this->alloc) {
            init(first, others...);
        }
        // copy/move ctors and assignment from another array

        // TODO: implement move semantics from an ArrayBase
        Array(const Array& other)
            : base(other), alloc(other.alloc),
            _arr(other._arr) {
            std::copy(sizes, sizes + dims, _sizes);
            arr = _arr.data();
            sizes = _sizes;
        }
        Array(Array&& other) : base(other), alloc(other.alloc),
            _arr(std::move(other._arr)) {
            std::copy(sizes, sizes + dims, _sizes);
            arr = _arr.data();
            sizes = _sizes;
        }
        Array(const base& other) : base(other), alloc(),
            _arr(arr, arr + rowsize * sizes[0]) {
            std::copy(sizes, sizes + dims, _sizes);
            arr = _arr.data();
            sizes = _sizes;
        }
        Array& operator=(const Array& other) {
            load(other);
            if (std::allocator_traits<
                Allocator>::propagate_on_container_copy_assignment::value) {
                alloc = other.alloc;
            }
            std::copy(sizes, sizes + dims, _sizes);
            _arr.assign(other._arr);
            arr = _arr.data();
            sizes = _sizes;
            return *this;
        }
        Array& operator=(Array&& other) {
            base::operator=(other);
            if (std::allocator_traits<
                Allocator>::propagate_on_container_move_assignment::value) {
                alloc = other.alloc;
            }
            std::copy(sizes, sizes + dims, _sizes);
            _arr = std::move(other._arr);
            arr = _arr.data();
            sizes = _sizes;
            return *this;
        }
        Array& operator=(const base& other) {
            base::operator=(other);
            std::copy(sizes, sizes + dims, _sizes);
            _arr.assign(arr, arr + sizes[0] * rowsize);
            arr = _arr.data();
            sizes = _sizes;
            return *this;
        }
    };

    /* syntactic sugar to help template deduction and avoid some (re-)typing
    mainly usefull for non default allocators */
    template <class T, class Allocator = std::allocator<T> >
    class ArrayBuilder {
    public:
        using size_type = typename allocator_traits<Allocator>::size_type;
        using difference_type
            = typename allocator_traits<Allocator>::difference_type;
    private:
        Allocator alloc;

    public:
        ArrayBuilder(const Allocator& alloc = Allocator()) : alloc(alloc) {}

        template <class ...U, int dims = sizeof...(U)+1>
        View<T, dims, size_type, difference_type> dynUseArray(T* arr,
            size_type first, U...others) {
            return View<T, dims, size_type, difference_type>(arr, first,
                others...);
        }
        template <class ...U, int dims = sizeof...(U)+1>
        Array<T, dims, Allocator> dynCopyArray(T* arr,
            size_type first, U...others) {
            return Array<T, dims, Allocator>(arr, alloc,
                first, others...);
        }
        template <class ...U, int dims = sizeof...(U)+1>
        Array<T, dims, Allocator> dynBuildArray(size_type first, U...others) {
            return Array<T, dims, Allocator>(alloc, first, others...);
        }
    };

    // iterator if cnst == 0 or const_iterator if cnst == 1, U is the value_type
    template <class T, int dims, class size_type,
        class difference_type, bool cnst, bool rev>
    class Iterator: public ElementAccessor<T, size_type, difference_type,
    difference_type, dims>{

    public:
        using value_type = typename std::conditional<cnst,
            typename std::conditional<dims == 1, const T,
            const Row<T, dims-1, size_type, difference_type>>::type,
            typename std::conditional<dims == 1, T, Row<T, dims - 1, size_type,
            difference_type>>::type>::type;
        using reference = typename std::conditional < dims == 1,
            value_type&, value_type>::type;
        using iterator_category = std::random_access_iterator_tag;

    private:

        struct Proxy {
            value_type elt;

            Proxy(value_type&& elt) : elt(elt) {}

            value_type* operator->() {
                return std::addressof(elt);
            }
        };
        T* arr;
        size_type *sizes;
        size_type rowsize;

        Iterator(T* arr, size_type *sizes, size_type rowsize) :
            arr(arr), sizes(sizes), rowsize(rowsize) {}

        template<bool x=false>
        reference getXElt(difference_type i) {
            if constexpr(x) {
                return this->getConstElt(arr - rowsize * (rev ? 1 : 0), sizes, rowsize, i);
            } else {
                return this->getElt(arr - rowsize * (rev ? 1 : 0), sizes, rowsize, i);
            }
        }
        void add(difference_type i) {
            arr += (rev ? -i : i) * rowsize ;
        }

        using iterator = Iterator<T, dims, size_type, difference_type, cnst, rev>;

    public:
        using pointer = Proxy;

        // a default ctor (to mimic standard iterators)
        Iterator(): arr(nullptr), sizes(nullptr), rowsize(0) {}

        //convert an (non const) iterator to a const_iterator
        //template <class X = T, typename = typename std::enable_if<cnst == 1>::type>
        Iterator(const Iterator<T, dims, size_type, difference_type, !cnst, rev>& other)
            : arr(other.arr), sizes(other.sizes), rowsize(other.rowsize)
        {
            static_assert(cnst, "Cannot remove const from iterator");
        }

        // all operations of an iterator
        reference operator*() {
            return getXElt(0) ;
        }
        pointer operator->() {
            return Proxy(getXElt(0));
        }
        const reference operator*() const {
            return getConstElt(arr - rowsize * (rev ? 1 : 0), sizes, rowsize, 0);
        }
        const pointer operator->() const {
            return Proxy(getXElt(0));
        }
        iterator& operator++() {
            this->add(1);
            return *this;
        }
        iterator& operator--() {
            this->add(-1);
            return *this;
        }
        iterator operator++(int) {
            iterator tmp = *this;
            this->add(1);
            return tmp;
        }
        iterator operator--(int) {
            iterator tmp = *this;
            this->add(-1);
            return tmp;
        }
        iterator& operator+=(difference_type i) {
            this->add(i);
            return *this;
        }
        iterator operator+(difference_type i) {
            iterator tmp = *this;
            tmp.add(i);
            return tmp;
        }
        iterator operator-=(difference_type i) {
            return operator+=(-i);
        }
        iterator operator-(difference_type i) {
            return operator+(-i);
        }

        value_type operator[](difference_type i) {
            return *(*this + i);
        }
        const value_type operator[](difference_type i) const {
            return *(*this + i);
        }

        // comparisons are allowed between const and non const iterators
        template <bool c>
        bool operator==(const Iterator<T, dims, size_type,
            difference_type, c, rev>& other) const {
            return (arr == other.arr) && (sizes == other.sizes)
                && (rowsize == other.rowsize);
        }
        template <bool c>
        bool operator!=(const Iterator<T, dims, size_type,
            difference_type, c, rev>& other) const {
            return !operator==(other);
        }
        template <bool c>
        bool operator<(const Iterator<T, dims, size_type,
            difference_type, rev, c>& other) const {
            return arr < other.arr;
        }
        template <bool c>
        bool operator>(const Iterator<T, dims, size_type,
            difference_type, c, rev>& other) const {
            return arr > other.arr;
        }
        template <bool c>
        bool operator<=(const Iterator<T, dims, size_type,
            difference_type, c, rev>& other) const {
            return !operator>(other);
        }
        template <bool c>
        bool operator>=(const Iterator<T, dims, size_type,
            difference_type, c, rev>& other) const {
            return !operator<(other);
        }

        friend class ArrayBase<T, dims, size_type, difference_type>;
        friend class Iterator<T, dims, size_type,
            difference_type, !cnst, rev>;
        friend class Row<T, dims - 1, size_type, difference_type>;
    };
}




#include <gtest/gtest.h>

#define AreEqual EXPECT_EQ
#define AreSame(a,b) EXPECT_EQ(&(a), &(b))
#define AreNotSame(a,b) EXPECT_NE(&(a), &(b))

using namespace DynArray;

/* creation of a view shall point to original raw array */
TEST(DynArray, useArr)
{
    ArrayBuilder<int> builder;
    int arr[60];
    int l = 0;
    for (int& i : arr) {
        i = l++;
    }
    auto dynarray = builder.dynUseArray(arr, 3, 4, 5);
    l = 0;
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            for (int k = 0; k < 5; k++) {
                AreSame(dynarray[i][j][k], arr[l++]);
            }
        }
    }
}
/* Assignment of a row from a second view */
TEST(DynArray, rowAssign)
{
    int arr[60];
    int l = 0;
    for (int& i : arr) {
        i = l++;
    }
    View<int, 3> array {arr, 3, 4, 5};

    int arr2[20];
    l = 64;
    for (int&i : arr) {
        i = l++;
    }
    View<int, 2> array2 { arr2, 4, 5 };

    array[1] = array2;
    /* controls that:
       - values have been copied
       - indices still point to original raw array
    */
    for (int i = 0; i < 4; i++) {
        for (int j = 0; j < 5; j++) {
            AreNotSame(array[1][i][j], array2[i][j]);
            AreEqual(array[1][i][j], array2[i][j]);
            AreSame(arr[20 + 5 * i + j], array[1][i][j]);
        }
    }
}
// swap Rows
// TEST(DynArray, rowSwap)
// {

//     int arr[60];
//     int l = 0;
//     for (int& i : arr) {
//         i = l++;
//     }
//     View<int, 3> array {arr, 3, 4, 5};

//     swap(array[0], array[2]);
//     l = 40;
//     int k = 0;
//     for (int i = 0; i < 4; i++) {
//         for (int j = 0; j < 5; j++) {
//             AreSame(arr[k++], array[0][i][j]);
//             AreEqual(l++, array[0][i][j]);
//         }
//     }
// }
//Assignement of a view from a Row or another View
TEST(DynArray, viewAssign)
{
    int arr[60];
    int l = 0;
    for (int& i : arr) {
        i = l++;
    }
    View<int, 3> array {arr, 3, 4, 5};

    View<int, 2> arr0 { array[0] };
    l = 0;
    for (int i = 0; i < 4; i++) {
        for (int j = 0; j < 5; j++) {
            AreSame(arr[l++], arr0[i][j]);
        }
    }
    View<int, 2> arr2 = array[2];
    arr0 = arr2;
    l = 40;
    for (int i = 0; i < 4; i++) {
        for (int j = 0; j < 5; j++) {
            AreSame(arr[l++], arr0[i][j]);
        }
    }
}
// swap views
TEST(DynArray, viewSwap)
{
    int arr[60];
    int l = 0;
    for (int& i : arr) {
        i = l++;
    }
    View<int, 3> array {arr, 3, 4, 5};

    View<int, 2> arr0 { array[0] };
    View<int, 2> arr2 = array[2];
    swap(arr0, arr2);
    l = 40;
    int k = 0;
    for (int i = 0; i < 4; i++) {
        for (int j = 0; j < 5; j++) {
            AreSame(arr[l++], arr0[i][j]);
            AreSame(arr[k++], arr2[i][j]);
        }
    }
}

TEST(DynArray, copyArr)
{
    ArrayBuilder<int> builder;
    int arr[60];
    int l = 0;
    for (int& i : arr) {
        i = l++;
    }
    auto dynarray = builder.dynCopyArray(arr, 3, 4, 5);
    l = 0;
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            for (int k = 0; k < 5; k++) {
                AreEqual(dynarray[i][j][k], arr[l]);
                AreNotSame(dynarray[i][j][k], arr[l]);
                l++;
            }
        }
    }
}

TEST(DynArray, buildArr)
{
    ArrayBuilder<int> builder;
    auto dynarray = builder.dynBuildArray(3, 4, 5);
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            for (int k = 0; k < 5; k++) {
                AreEqual(dynarray[i][j][k], 0);
            }
        }
    }
}

TEST(DynArray, copyCtor)
{
    ArrayBuilder<int> builder;
    int arr[60];
    int l = 0;
    for (int& i : arr) {
        i = l++;
    }
    auto dynarray = builder.dynUseArray(arr, 3, 4, 5);
    Array<int, 3> dyn2 { dynarray };
    l = 0;
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            for (int k = 0; k < 5; k++) {
                AreEqual(dyn2[i][j][k], arr[l]);
                AreNotSame(dyn2[i][j][k], arr[l]);
                l++;
            }
        }
    }
}

TEST(DynArray, moveCtor)
{
    ArrayBuilder<int> builder;
    int arr[60];
    int l = 0;
    for (int& i : arr) {
        i = l++;
    }
    auto dynarray = builder.dynCopyArray(arr, 3, 4, 5);
    int *ix = dynarray.data();
    auto dyn2 = std::move(dynarray);
    AreEqual(ix, dyn2.data());
    l = 0;
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            for (int k = 0; k < 5; k++) {
                AreEqual(dyn2[i][j][k], arr[l]);
                l++;
            }
        }
    }
}

TEST(DynArray, copyAssign)
{
    ArrayBuilder<int> builder;
    int arr[60];
    int l = 0;
    for (int& i : arr) {
        i = l++;
    }
    auto dynarray = builder.dynUseArray(arr, 3, 4, 5);
    auto dyn2 = builder.dynBuildArray(3, 4, 5);
    dyn2 = dynarray;
    l = 0;
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            for (int k = 0; k < 5; k++) {
                AreEqual(dyn2[i][j][k], arr[l]);
                AreNotSame(dyn2[i][j][k], arr[l]);
                l++;
            }
        }
    }
}

TEST(DynArray, moveAssign)
{
    ArrayBuilder<int> builder;
    int arr[60];
    int l = 0;
    for (int& i : arr) {
        i = l++;
    }
    auto dynarray = builder.dynCopyArray(arr, 3, 4, 5);
    int *ix = dynarray.data();
    auto dyn2 = builder.dynBuildArray(3, 4, 5);
    dyn2 = std::move(dynarray);
    l = 0;
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            for (int k = 0; k < 5; k++) {
                AreEqual(dyn2[i][j][k], arr[l]);
                l++;
            }
        }
    }
    AreEqual(ix, dyn2.data());
}

// TEST(DynArray, nonConstIter)
// {
//     ArrayBuilder<int> builder;
//     int arr[60];
//     int l = 0;
//     for (int& i : arr) {
//         i = l++;
//     }
//     auto dynarray = builder.dynUseArray(arr, 3, 4, 5);
//     l = 0;
//     for (auto& it1 : dynarray) {
//         for (auto& it2 : it1) {
//             for (auto& it3 : it2) {
//                 AreSame(it3, arr[l]);
//                 l++;
//                 it3 = l;             // control it is not const...
//             }
//         }
//     }
// }

TEST(DynArray, constIter)
{
    ArrayBuilder<int> builder;
    int arr[60];
    int l = 0;
    for (int& i : arr) {
        i = l++;
    }
    auto dynarray = builder.dynUseArray(arr, 3, 4, 5);
    l = 0;
    for (auto it1 = dynarray.cbegin(); it1 != dynarray.cend(); it1++) {
        for (auto it2 = it1->cbegin(); it2 != it1->cend(); it2++) {
            for (auto it3 = it2->cbegin(); it3 != it2->cend(); it3++) {
                AreSame(*it3, arr[l]);
                l++;
                // *it3 = l;           // does not compile
            }
        }
    }
}

TEST(DynArray, convConstIterator)
{
    ArrayBuilder<int> builder;
    int arr[60];
    int l = 0;
    for (int& i : arr) {
        i = l++;
    }
    auto dynarray = builder.dynUseArray(arr, 3, 4, 5);

    auto it = dynarray.begin();
    Array<int, 3>::const_iterator cit = it;

    //it = (MDynArray<int, 3>::iterator) cit;  // does not compile

    it += 1;
    cit += 1;
    EXPECT_GT(it, dynarray.begin());
    EXPECT_EQ(it, cit);
    EXPECT_EQ(cit, it);
}

TEST(DynArray, revIterator)
{
    ArrayBuilder<int> builder;
    int arr[60];
    int l = 0;
    for (int& i : arr) {
        i = l++;
    }
    auto dynarray = builder.dynUseArray(arr, 3, 4, 5);
    l = 0;

    for (auto it1 = dynarray.rbegin(); it1 != dynarray.rend(); it1++) {
        for (auto it2 = it1->rbegin(); it2 != it1->rend(); it2++) {
            for (auto it3 = it2->rbegin(); it3 != it2->rend(); it3++) {
                AreSame(*it3, arr[59 - l]);
                l++;
                *it3 = l;           // control non constness
            }
        }
    }
}

TEST(DynArray, rowToIter)
{
    ArrayBuilder<int> builder;
    int arr[60];
    int l = 0;
    for (int& i : arr) {
        i = l++;
    }
    auto dynarray = builder.dynUseArray(arr, 3, 4, 5);
    l = 0;

    auto row = dynarray[1];
    auto it = &row - 1;
    AreSame(arr[0], it[0][0][0]);

}
\$\endgroup\$
  • 2
    \$\begingroup\$ I have to apologise that I haven't yet reached the point of reviewing the actual code. But hope this might be useful to other reviewers, and perhaps aid portability in the original. \$\endgroup\$ – Toby Speight Nov 9 '18 at 10:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.