Skip to main content
Add unit tests per Toby Speight's comment
Source Link

Unit tests

After Toby Speight'comment, I have realized that test could be helpful here. Here is the current tests using MSVC 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:
        
        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++]);
                    }
                }
            }
        }

        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);
            auto 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.dynUseArray(arr, 3, 4, 5);
            auto dyn2 = std::move(dynarray);
            Assert::AreEqual(dynarray.size(), 0u);
            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(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.dynUseArray(arr, 3, 4, 5);
            auto dyn2 = builder.dynBuildArray(3, 4, 5);
            dyn2 = std::move(dynarray);
            Assert::AreEqual(dynarray[1][1][1], 0);   // Beware implementation test
            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(dyn2[i][j][k], arr[l]);
                        l++;
                    }
                }
            }
        }

        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();
            MDynArray<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
                    }
                }
            }
        }

    };
}

Unit tests

After Toby Speight'comment, I have realized that test could be helpful here. Here is the current tests using MSVC 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:
        
        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++]);
                    }
                }
            }
        }

        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);
            auto 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.dynUseArray(arr, 3, 4, 5);
            auto dyn2 = std::move(dynarray);
            Assert::AreEqual(dynarray.size(), 0u);
            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(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.dynUseArray(arr, 3, 4, 5);
            auto dyn2 = builder.dynBuildArray(3, 4, 5);
            dyn2 = std::move(dynarray);
            Assert::AreEqual(dynarray[1][1][1], 0);   // Beware implementation test
            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(dyn2[i][j][k], arr[l]);
                        l++;
                    }
                }
            }
        }

        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();
            MDynArray<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
                    }
                }
            }
        }

    };
}
Edit for adding the precision that MDynIterator is a stashing iterator.
Source Link

After Incomputable's comment, I have realized that MDynIterator did not fullfill the requirements for a bidirectional iterator because it was a stashing iterator. I do not want to change the code immediately but it will be declared as a simple forward iterator. But that also means that std::reverse_iterator adaptor is not usable here...

After Incomputable's comment, I have realized that MDynIterator did not fullfill the requirements for a bidirectional iterator because it was a stashing iterator. I do not want to change the code immediately but it will be declared as a simple forward iterator. But that also means that std::reverse_iterator adaptor is not usable here...

Tweeted twitter.com/StackCodeReview/status/1008039833539956738
Source Link

Multi-dimensional container using operator[]

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

###Context:

I want to have a tool that allows to use all the C++ goodies (algorithms, for loop on containers, etc.) on 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.

I ended with a bunch a classes:

  • a full container class (MDynArray) with all copy/move semantics provided the underlying type has - in order to mimic standard containers, allocator is configurable
  • a sub objects class (MSubArray) which accesses (read-write) the memory of its parent and tries to mimic the operations of a sequencial direct access standard container (operator[] and iterators)
  • iterator and const_iterator classes for MSubArray (MDynArray being a subclass of it)
  • a builder class (ArrayBuilder), because I want to be able to
  • build a new dynamic array from scratch
  • have it copy an existing raw array
  • have it use (no copy here) an existing raw array taking or not ownership

and could not find a way to declare constructors or static factory methods for those different use cases

Code

Here is my current code (yes it is quite long...)

#include <type_traits>
#include <algorithm>
#include <exception>
#include <stdexcept>
#include <memory>
#include <utility>

namespace DynArray {

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

    //Forward declarations
    template <class T, int dims, class Allocator>
    class MDynIteratorBase;
    template <class T, int dims, int cnst, class Allocator,
        class U = typename std::conditional<cnst == 1,
        const MSubArray<T, dims, Allocator>, MSubArray<T, dims, Allocator>>::type>
    class MDynIterator;

    // Base: contains the data and declares types
    template <class T, int dims, class Allocator>
    class MDynArrayBase {
    public:
        using value_type = T;
        using allocator_type = Allocator;
        using pointer = typename allocator_traits<Allocator>::pointer;
        using const_pointer = typename allocator_traits<Allocator>::const_pointer;
        using reference = T&;
        using const_reference = const T&;
        using size_type = typename allocator_traits<Allocator>::size_type;
        using difference_type = typename allocator_traits<Allocator>::difference_type;
    protected:
        T* arr;
        size_type *sizes;
        size_type rowsize;

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

    public:
        virtual ~MDynArrayBase() {}
    };

    // Sub array: all the logic of accesses but do not manage (de-/)allocation
    template <class T, int dims, class Allocator>
    class MSubArray : public MDynArrayBase<T, dims, Allocator> {
    protected:
        using MDynArrayBase<T, dims, Allocator>::arr;
        using MDynArrayBase<T, dims, Allocator>::sizes;
        using MDynArrayBase<T, dims, Allocator>::rowsize;

        MSubArray(T* arr, size_type* sizes, size_type rowsize)
            : MDynArrayBase<T, dims, Allocator>(arr, sizes, rowsize) {}

    public:
        using iterator = typename MDynIterator<T, dims-1, 0, Allocator>;
        using const_iterator = typename MDynIterator<T, dims-1, 1, Allocator>;

        // access to member
        MSubArray<T, dims-1, Allocator> operator[] (size_type i) {
            MSubArray<T, dims-1, Allocator> child(arr + rowsize * i,
                sizes + 1, rowsize / sizes[1]);
            return child;
        }
        const MSubArray<T, dims-1, Allocator> operator[] (size_type i) const {
            MSubArray<T, dims-1, Allocator> child(arr + rowsize * i,
                sizes + 1, rowsize / sizes[1]);
            return child;
        }

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

        // iterators
        iterator begin() {
            return iterator(arr, sizes + 1,
                rowsize / sizes[1]);
        }
        iterator end() {
            iterator tmp = begin();
            tmp += sizes[0];
            return tmp;
        }
        const_iterator cbegin() const {
            return const_iterator(arr, sizes + 1,
                rowsize / sizes[1]);
        }
        const_iterator cend() const {
            const_iterator tmp = cbegin();
            tmp += sizes[0];
            return tmp;
        }
        friend class MSubArray<T, dims + 1, Allocator>;
        friend class iterator;
        friend class const_iterator;
        friend class MDynIteratorBase<T, dims, Allocator>;
    };

    // specialization for 1D: members are true T objects
    template <class T, class Allocator>
    class MSubArray<T, 1, Allocator> : public MDynArrayBase<T, 1, Allocator> {
    protected:
        using MDynArrayBase<T, 1, Allocator>::arr;
        using MDynArrayBase<T, 1, Allocator>::sizes;

        MSubArray(T* arr, size_type* sizes, size_type rowsize)
            : MDynArrayBase<T, 1, Allocator>(arr, sizes, rowsize) {}

    public:
        using iterator = typename T*;
        using const_iterator = typename const T*;

        ~MSubArray() {}
        T& operator[] (size_type i) {
            return arr[i];
        }
        const T& operator[] (size_type i) const {
            return arr[i];
        }

        // same for size and arr, dims
        size_t size(size_t i = 0) {
            if (i != 0) {
                throw std::out_of_range("Illegal dimension");
            }
            if (sizes == nullptr) return 0;
            return sizes[0];
        }
        size_type tot_size() const {
            if (sizes == nullptr) return 0;
            return sizes[0];
        }
        T* data() {
            return arr;
        }
        const T* data() const {
            return arr;
        }
        constexpr int getdims() const {
            return 1;
        }

        //iterators
        iterator begin() {
            return arr;
        }
        iterator end() {
            return arr + sizes[0];
        }
        const_iterator cbegin() const {
            return arr;
        }
        const_iterator cend() const {
            return arr + sizes[0];
        }
        friend class MSubArray<T, 2, Allocator>;
        friend class iterator;
        friend class const_iterator;
        friend class MDynIteratorBase<T, 1, Allocator>;
    };

    // forward declaration for the builder class
    template <class T, class Allocator = std::allocator<T> >
    class ArrayBuilder;

    // Full array, must manage allocation/deallocation of resources
    template <class T, int dims, class Allocator = allocator<T> >
    class MDynArray : public MSubArray<T, dims, Allocator> {
        using MSubArray<T, dims, Allocator>::arr;
        using MSubArray<T, dims, Allocator>::sizes;
        using MSubArray<T, dims, Allocator>::rowsize;

        bool ownarr;          // if true, arr have to be deleted
        Allocator alloc;      // internal allocator

        // allocates a T array and optionaly copy-initializes its elements
        static T* clone(T* src, size_type tot, Allocator alloc) {
            T* dst = allocator_traits<Allocator>::rebind_traits<T>::allocate(
                alloc, tot);
            size_type i;
            try {
                if (src != nullptr) {
                    for (i = 0; i < tot; i++) {
                        allocator_traits<Allocator>::rebind_traits<T>::construct(
                            alloc, dst + i, src[i]);
                    }
                }
                else {
                    for (i = 0; i < tot; i++) {
                        allocator_traits<Allocator>::rebind_traits<T>::construct(
                            alloc, dst + i);
                    }
                }
            }
            catch(std::exception &) {
                while (i-- > 0) {
                    allocator_traits<Allocator>::rebind_traits<T>::destroy(
                        alloc, dst + i);
                }
                allocator_traits<Allocator>::rebind_traits<T>::deallocate(alloc,
                    dst, tot);
                throw;
            }
            return dst;
        }

        MDynArray(T* arr, size_type* sizes, size_type rowsize, bool ownarr,
            const Allocator& alloc)
            : MSubArray<T, dims, Allocator>(arr, sizes, rowsize),
            ownarr(ownarr), alloc(alloc) {}
    public:
        // copy/move ctors and assignment (rule of 5)
        MDynArray(const MDynArray<T, dims, Allocator>& other) 
            : MSubArray(nullptr, nullptr, 0) {
            alloc = other.alloc;
            ownarr = true;
            sizes = new size_type[dims];
            std::copy(other.sizes, other.sizes + dims, sizes);
            rowsize = other.rowsize;
            try {
                arr = clone(other.arr, rowsize * sizes[0], alloc);
            }
            catch (std::exception&) {
                delete[] sizes;
            }
        }
        MDynArray(MDynArray<T, dims, Allocator>&& other)
            : MSubArray(nullptr, nullptr, 0), alloc(Allocator()), ownarr(false) {
            swap(other);
        }
        MDynArray<T, dims, Allocator>& operator = (
            const MDynArray<T, dims, Allocator>& other) {
            MDynArray<T, dims, Allocator> tmp(other);
            swap(tmp);
            return *this;
        }
        MDynArray<T, dims, Allocator>& operator = (
            MDynArray<T, dims, Allocator>&& other) {
            swap(other);
            return *this;
        }
        ~MDynArray() {
            if (ownarr) {
                delete[] arr;
            }
            delete[] sizes;
        }
        void swap(MDynArray<T, dims, Allocator>& other) {
            using std::swap;

            swap(arr, other.arr);
            swap(sizes, other.sizes);
            swap(rowsize, other.rowsize);
            swap(ownarr, other.ownarr);
            swap(alloc, other.alloc);
        }
        friend class ArrayBuilder<T, Allocator>;
    };

    // auxilliary class to build new MDynArray objects, possibly copying
    //  moving (take ownership) or just using a pre-existing array
    template <class T, class Allocator>
    class ArrayBuilder {
    public:
        using size_type = typename allocator_traits<Allocator>::size_type;
    private:
        Allocator alloc;

        template <class...U>
        static size_type calc_size(size_type *sizes, size_type first,
            U...others) {
            if (sizes != nullptr) *sizes = first;
            return first * calc_size(sizes + 1, others...);
        }
        static size_type calc_size(size_type *sizes, size_type first) {
            if (sizes != nullptr) *sizes = first;
            return first;
        }
    public:
        ArrayBuilder(const Allocator& alloc = Allocator()) : alloc(alloc) {}

        template <class T, class ...U>
        MDynArray<T, sizeof...(U)+1, Allocator> dynUseArray(T* arr,
            size_type first, U...others) {
            constexpr size_t dims = sizeof...(U)+1;
            size_type *sizes = new size_type[dims];
            size_type tot = calc_size(sizes, first, others...);
            size_type rowsize = tot / sizes[0];
            return MDynArray<T, dims, Allocator>(arr, sizes, rowsize,
                false, alloc);
        }
        template <class T, class ...U>
        typename std::enable_if<std::is_copy_constructible<T>::value,
            MDynArray<T, sizeof...(U)+1, Allocator>>::type
            dynCopyArray(T* arr, size_type first, U...others) {
            constexpr size_t dims = sizeof...(U)+1;
            size_type *sizes = new size_type[dims];
            size_type tot = calc_size(sizes, first, others...);
            T* dst;
            try {
                dst = MDynArray<T, dims, Allocator>::clone(arr, tot, alloc);
            }
            catch (std::exception&) {
                delete[] sizes;
                throw;
            }
            return MDynArray<T, sizeof...(U)+1, Allocator>(dst, sizes,
                tot / sizes[0], true, alloc);
        }
        template <class ...U>
            MDynArray<T, sizeof...(U)+1, Allocator>
            dynBuildArray(size_type first, U...others) {
            constexpr size_t dims = sizeof...(U)+1;
            size_type *sizes = new size_type[dims];
            size_type tot = calc_size(sizes, first, others...);
            T* dst;
            try {
                dst = MDynArray<T, dims, Allocator>::clone(nullptr, tot, alloc);
            }
            catch (std::exception&) {
                delete[] sizes;
                throw;
            }
            return MDynArray<T, sizeof...(U)+1, Allocator>(dst, sizes,
                tot / sizes[0], true, alloc);
        }
        template <class T, class ...U>
            MDynArray<T, sizeof...(U)+1, Allocator> dynMoveArray(T* arr,
                size_type first, U...others) {
            MDynArray<T, dims, Allocator> tmp = dynUseArray(arr,
                first, other...);
            tmp.ownarr = true;
            return tmp;
        }
    };

    // base class for both iterator and const_interator to ease comparisons
    template <class T, int dims, class Allocator>
    class MDynIteratorBase {
        using itbase = typename MDynIteratorBase<T, dims, Allocator>;

    public:
        using size_type = typename allocator_traits<Allocator>::size_type;
        using difference_type =
            typename allocator_traits<Allocator>::difference_type;
        
    protected:
        MSubArray<T, dims, Allocator> elt;
        size_type sz;

        MDynIteratorBase(T* arr, size_type *sizes, size_type rowsize) :
            elt(arr, sizes, rowsize), sz(sizes[0] * rowsize) {}

    public:
        bool operator ==(const itbase& other) const {
            return (elt.arr == other.elt.arr) && (elt.sizes == other.elt.sizes)
                && (elt.rowsize == other.elt.rowsize);
        }
        bool operator != (const itbase& other) const {
            return !operator ==(other);
        }
        bool operator <(const itbase& other) const {
            return elt.arr < other.elt.arr;
        }
        bool operator >(const itbase& other) const {
            return elt.arr > other.elt.arr;
        }
        bool operator <=(const itbase& other) const {
            return !operator >(other);
        }
        bool operator >=(const itbase& other) const {
            return !operator <(other);
        }

    protected:
        itbase& add(difference_type i) {  // implemented once in the base class
            elt.arr += i * sz;
            return *this;
        }

    };

    // iterator if cnst == 0 or const_iterator if cnst == 1, U is the value_type
    template <class T, int dims, int cnst, class Allocator, class U>
    class MDynIterator: public MDynIteratorBase<T, dims, Allocator> {
        using base = typename MDynIteratorBase<T, dims, Allocator>;
        using base::elt;
        using iterator = typename MDynIterator<T, dims, cnst, Allocator, U>;

        using difference_type = typename base::difference_type;
        using value_type = typename U;
        using pointer = typename U*;
        using reference = typename U&;
        using iterator_category = std::random_access_iterator_tag;
        
        MDynIterator(T* arr, size_type *sizes, size_type rowsize) :
            base(arr, sizes, rowsize) {}

    public:

        // a default ctor (to mimic standard iterators)
        MDynIterator(): base(nullptr, nullptr, 0) {}

        //convert an (non const) iterator to a const_iterator
        template <class X = T, typename = std::enable_if<cnst == 1>::type>
        MDynIterator(MDynIterator<T, dims, 1 - cnst, Allocator>& other)
            : base(other) {}

        // all operations of an iterator
        reference operator * () {
            return elt;
        }
        pointer operator -> () {
            return &elt;
        }
        const reference operator * () const {
            return elt;
        }
        const pointer operator -> () const {
            return &elt;
        }
        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);
        }

        friend class MSubArray<T, dims+1, Allocator>;
    };
}

Questions:

I tried hard to remain as close as I could to standard components (containers and iterators) but may have followed a wrong path somewhere and would really like to know where

I tried to follow modern C++ patterns but coming from good old C I may have fallen in some anti pattern and would really like to know

I would anyway be interested by any improvement

Disclaimer:

Reverse iterators are (still) not implemented. I may implement them in a later version but the question is a priori not about that point.