StaticMatrix constructor taking a 2D std::array

Issue #322 resolved
Matthias Moulin created an issue

StaticVector contains the following constructors:

template< typename Other, size_t Dim >
constexpr StaticVector& operator=( const std::array<Other,Dim>& array );

constexpr StaticVector& operator=( initializer_list<Type> list );

StaticMatrix contains the following constructor:

constexpr StaticMatrix( initializer_list< initializer_list<Type> > list );

Is it possible to add a

template< typename Other, size_t Rows, size_t Cols >
constexpr StaticMatrix( const std::array<std::array<Other,Cols>,Rows>& array );

as well? Note that this improves the consistency with regard to the constructors ofStaticVector.

Furthermore, is it possible to add structure binding support for StaticMatrix as well to access rows represented as the corresponding StaticVector type. Note that this improves the consistency with regard to the constructors ofStaticMatrix and with regard to the structure binding support for StaticVector.

Comments (8)

  1. Klaus Iglberger

    Hi Matthias!

    Thanks for creating this issue. The proposal to add another constructor to StaticMatrix sounds reasonable and we’ll see what we can do. We’re not so sure about the structured binding support as this might not be “expected” behavior, but we’ll see what we can do. Thanks again,

    Best regards,

    Klaus!

  2. Matthias Moulin reporter

    For the structure binding support, just returning a StaticVector by value in the std::get template specializations would suffice. Or something more heavier that still allows assigning rows of the original matrix.

    For my own use case, the latter is not important. Having structure binding / tuple support allows me to create objects from other objects which types do not need to be known and which headers thus not need to be included.

    template< typename ToT, TupleLike TupleT, std::size_t... Is >
    [[nodiscard]]
    constexpr ToT FromTuple(TupleT&& arg, std::index_sequence< Is... >)
    {
        using std::get; // ADL
        return ToT{ get< Is >(std::forward< TupleT >(arg))... };
    }
    
    template< typename ToT, TupleLike TupleT >
    [[nodiscard]]
    constexpr ToT FromTuple(TupleT&& arg)
    {
        constexpr auto FromN = std::tuple_size_v< tuple_decay_t< TupleT > >;
        return FromTuple< ToT >(std::forward< TupleT >(arg),
                                std::make_index_sequence< FromN  >());
    }
    

    More concretely, I could create Arrays (wrapper of std::array with constructors) from Blaze’s StaticVector and StaticMatrix:

    Array< float, 2u > pair = std::pair< float, float >{};
    Array< float, 3u > array = std::array< float, 3u >{};
    Array< float, 4u > vector = blaze::StaticVector< float, 4u >{};
    Array< Array< float, 3u >, 4u > vector = blaze::StaticMatrix< float, 4u, 3u >{};
    

    etc. for even higher dimensional arrays.

  3. Klaus Iglberger

    Hi Matthias!

    Commit 1171f6f adds a constructor and assignment operator for std::array to the StaticMatrix class template. I did not, however, add structure binding support. From my point of view it is unintuitive which kind of vector this should produce (rows vs. columns). Since you’re able to add the necessary specializations yourself without having to touch Blaze code, I consider a custom solution as sufficient.

    Best regards,

    Klaus!

  4. Klaus Iglberger

    Summary

    Commits 1171f6f, 36bc03d, d1c7bcb, and 06c0631 add constructors and assignment operators for std::array to the StaticMatrix, HybridMatrix, DynamicMatrix and CustomMatrix class template. The feature is immediately available via cloning the Blaze repository and will be officially released in Blaze 3.7.

    Array Construction

    All dense matrix classes offer a constructor for an initialization with a dynamic or static array, or with a std::array. If the matrix is initialized from a dynamic array, the constructor expects the dimensions of values provided by the array as first and second argument, the array as third argument. In case of a static array or std::array, the fixed size of the array is used:

    const std::unique_ptr<double[]> array1( new double[6] );
    // ... Initialization of the dynamic array
    blaze::StaticMatrix<double,2UL,3UL> M12( 2UL, 3UL, array1.get() );
    
    int array2[2][2] = { { 4, -5 }, { -6, 7 } };
    blaze::StaticMatrix<int,2UL,2UL,rowMajor> M13( array2 );
    
    const std::array<std::array<float,3UL>,2UL> array3{ { { 1, 2, 3 }, { 4, 5, 6 } } };
    blaze::StaticMatrix<int,2UL,3UL> M14( array3 );
    
  5. Matthias Moulin reporter

    Hey Klaus,

    Thanks for adding this generally useful functionality!

    P.S.: What is the C++ rationale for providing overloaded assignment operators as well instead of relying on the implicit constructor + move assignment operator to perform the same assignment?

    P.S.2: What is the reason to template the constructor accepting an array or std::array on the array size and validating afterwards, as these are already know for StaticVector and StaticMatrix.

    For the structure binding, I use the ad hoc solution below. Imho (and according to my own rationale 😉 ), get should return a row vector independent of (i) the matrix storage order and (ii) the general transpose flag preference for vectors, because:

    • matrix(i,j) returns the matrix element at row i, column j;
    • get<j>(get<i>(matrix)) returns the matrix element at row i, column j;
    • get<i>(matrix) is closer to the “common” mathematical notation matrixi, compared to matrix,i .

    But I can understand the ambiguity.

    namespace blaze
    {
        template< std::size_t I, typename T, std::size_t M, std::size_t N, bool SO >
        constexpr StaticVector< T, M, rowVector >
            get(StaticMatrix< T, M, N, SO >& v) noexcept
        {
            return row< I >(v);
        }
    
        template< std::size_t I, typename T, std::size_t M, std::size_t N, bool SO >
        constexpr StaticVector< T, M, rowVector >
            get(StaticMatrix< T, M, N, SO >&& v) noexcept
        {
            return row< I >(std::move(v));
        }
    
        template< std::size_t I, typename T, std::size_t M, std::size_t N, bool SO >
        constexpr StaticVector< T, M, rowVector >
            get(const StaticMatrix< T, M, N, SO >& v) noexcept
        {
            return row< I >(v);
        }
    
        template< std::size_t I, typename T, std::size_t M, std::size_t N, bool SO >
        constexpr StaticVector< T, M, rowVector >
            get(const StaticMatrix< T, M, N, SO >&& v) noexcept
        {
            return row< I >(std::move(v));
        }
    }
    
    namespace std
    {
        template< typename T, size_t M, size_t N, bool SO >
        class tuple_size< blaze::StaticMatrix< T, M, N, SO > >
            : public integral_constant< size_t, M >
        {};
    
        template< size_t I, typename T, size_t M, size_t N, bool SO >
        class tuple_element< I, blaze::StaticMatrix< T, M, N, SO > >
        {
    
        public:
    
            using type = blaze::StaticVector< T, M, blaze::rowVector >;
        };
    }
    

  6. Matthias Moulin reporter

    The following sample (https://godbolt.org/z/6m6E8c) still puzzles me:

    #include <array>
    #include <blaze/math/StaticMatrix.h>
    #include <type_traits>
    
    template< typename T >
    struct array
    {
        using type = T;
    };
    
    template< typename T >
    using array_t = typename array< T >::type;
    
    template< typename T, std::size_t N >
    struct Array : std::array< T, N >
    {
        using array_type = std::array< array_t< T >, N >;
    
        constexpr operator array_type() const
        {
            return {};
        }
    };
    
    template< typename T, std::size_t N >
    struct array< Array< T, N > >
    {
        using type = std::array< array_t< T >, N >;
    };
    
    using S32          = int;
    using ArrayS32x2   = Array< S32, 2u >;
    using ArrayS32x2x2 = Array< ArrayS32x2, 2u >;
    using arrayS32x2   = std::array< S32, 2u >;
    using arrayS32x2x2 = std::array< std::array< S32, 2u >, 2u >;
    using StaticS32x2  = blaze::StaticMatrix< S32, 2u, 2u >;
    
    static_assert(std::is_same_v< array_t< S32 >          , S32          >);
    static_assert(std::is_same_v< array_t< ArrayS32x2 >   , arrayS32x2   >);
    static_assert(std::is_same_v< array_t< ArrayS32x2x2 > , arrayS32x2x2 >);
    static_assert(std::is_same_v< ArrayS32x2::array_type  , arrayS32x2   >);
    static_assert(std::is_same_v< ArrayS32x2x2::array_type, arrayS32x2x2 >);
    
    int main() 
    {
        constexpr ArrayS32x2x2 a = {};
        StaticS32x2 m1(static_cast< arrayS32x2x2 >(a));
    
        StaticS32x2 m2(a); // Why is the implicit cast/conversion operator not invoked?
    
        return 0;
    }
    

    I assumed an implicit cast/conversion operator could have solved the case of inheriting from std::array for the 1D< case.

  7. Log in to comment