Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++11 dynamic multidimensional array of any type using vector/initilizer list

How do you create a multidimensional array (matrix) whose dimensions are determined at runtime.

The best way seems to be to take a vector of dimensions for construction and also a vector of offsets to access individual elements

This will also allow using initializer lists:

This should take a matrix of types determined at compile time, so templates make sense

C++11 features should be used as appropriate, bonus points for lambdas

Example usage:

int main(int, char **)
{
        static const std::size_t d1{2};
        static const std::size_t d2{3};
        static const std::size_t d3{4};
        multi_vec<std::size_t> q({d1,d2,d3});

        for (std::size_t i1=0; i1 < d1; ++i1)
                for (std::size_t i2=0; i2 < d2; ++i2)
                        for (std::size_t i3=0; i3 < d3; ++i3)
                                q[{i1,i2,i3}]=foo(i1,i2,i3);

        for (std::size_t i1=0; i1 < d1; ++i1)
                for (std::size_t i2=0; i2 < d2; ++i2)
                        for (std::size_t i3=0; i3 < d3; ++i3)
                                std::cout << "{"
                                        << i1 << ","
                                        << i2 << ","
                                        << i3 << "}=> "
                                        << foo(i1,i2,i3) << "=="
                                        << q[{i1,i2,i3}]
                                        << ((foo(i1,i2,i3) == q[{i1,i2,i3}])
                                              ? " good" : " ERROR")
                                << std::endl;
};
like image 260
Glenn Teitelbaum Avatar asked Oct 21 '22 17:10

Glenn Teitelbaum


1 Answers

This is actually a good place to use lambdas with std::for_each and unique_ptr for memory management:

template <class T>
class multi_vec
{
public:
    using param=std::vector<size_t>;

    explicit multi_vec(const param& dimensions)
        : dim{dimensions}, prod {1}
    {
        std::for_each(dim.begin(), dim.end(), [this] (std::size_t val)
        {
            mult.emplace_back(prod);
            prod *= val;
        } );
        ptr.reset(new T[prod]);
    }
    std::size_t capacity() const { return prod; }

    // undefined if elements in lookup != elemenets in dim
    // undefined if any element in lookup
        // is greater than or equal to corresponding dim element
    T& operator[](const param& lookup)
    {
        return ptr[get_offset(lookup)];
    }
    const T operator[](const param& lookup) const
    {
        return ptr[get_offset(lookup)];
    }
private:
    std::size_t get_offset(const param& lookup) const
    {
        std::size_t offset=0;
        auto mit=mult.begin();
        std::for_each(lookup.begin(), lookup.end(), [&offset, &mit] (std::size_t val)
        {
            offset+=*mit * val;
           ++mit;
        } );
        return offset;
    }
    param dim;
    param mult;
    std::size_t prod;
    std::unique_ptr<T[]> ptr;
};

This uses a single dimensional array for actual storage and caches multipliers for reuse Since a normal multidimension array e.g. x[2][3][4] does not bounds check, going out of bounds is deemed undefined instead of constantly checking values that are probably valid. In the same way diminsionality is not checked for lookups, if required, a static member can be added to check if a parameter is valid, in terms of bounds or dimensionality.

like image 197
Glenn Teitelbaum Avatar answered Oct 30 '22 01:10

Glenn Teitelbaum