mdspan - basic_mdspan and mdspan declaration
authorChristian Blau <cblau@gwdg.de>
Tue, 4 Dec 2018 11:53:58 +0000 (12:53 +0100)
committerErik Lindahl <erik.lindahl@gmail.com>
Wed, 26 Dec 2018 15:43:58 +0000 (16:43 +0100)
basic_mdspan provides the core functionality for multidimensional array
indexing by combining multidimensional index evaluation, and memory
access.

mdspan is defined as basic_mdspan with wrapped indices, basic_accessor
policy and right-aligned memory layout.

Change-Id: I3eacb734e35a977387c5f023afe636f78ee64eac

src/gromacs/mdspan/README
src/gromacs/mdspan/mdspan.h [new file with mode: 0644]
src/gromacs/mdspan/tests/CMakeLists.txt
src/gromacs/mdspan/tests/mdspan.cpp [new file with mode: 0644]

index 56607fb1277ffc0d2dfadc4f7fbbc3da84e6881e..f77f0945fc7c803c4f5ffb198fce5f373da81446 100644 (file)
@@ -18,3 +18,8 @@ The following modifications were carried out:
 * use minimial includes in all header files instead of just including mdspan
 * move tests into a subdirectory tests and rename test_foo.cpp to foo.cpp
 * renamed tests and test fixture class to match GROMACS conventions
+* changed variable signature to match proposal P0009r8 where reference
+  implementation differed, (const to const & in constructors of basic_mdspan)
+* Updated the mdspan test to a typed test
+* added test for basic accessor policy
+* added test for mdspan declaration
diff --git a/src/gromacs/mdspan/mdspan.h b/src/gromacs/mdspan/mdspan.h
new file mode 100644 (file)
index 0000000..295efa3
--- /dev/null
@@ -0,0 +1,277 @@
+/*
+ * This file is part of the GROMACS molecular simulation package.
+ *
+ * Copyright (c) 2018, by the GROMACS development team, led by
+ * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
+ * and including many others, as listed in the AUTHORS file in the
+ * top-level source directory and at http://www.gromacs.org.
+ *
+ * GROMACS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ *
+ * GROMACS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GROMACS; if not, see
+ * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+ *
+ * If you want to redistribute modifications to GROMACS, please
+ * consider that scientific software is very special. Version
+ * control is crucial - bugs must be traceable. We will be happy to
+ * consider code for inclusion in the official distribution, but
+ * derived work must not be called official GROMACS. Details are found
+ * in the README & COPYING files - if they are missing, get the
+ * official version at http://www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the research papers on the package. Check out http://www.gromacs.org.
+ */
+/*
+ * This file is a modified version of original work of Sandia Corporation.
+ * In the spirit of the original code, this particular file can be distributed
+ * on the terms of Sandia Corporation.
+ */
+/*
+ *                          Kokkos v. 2.0
+ *               Copyright (2014) Sandia Corporation
+ *
+ * Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
+ * the U.S. Government retains certain rights in this software.
+ *
+ * Kokkos is licensed under 3-clause BSD terms of use:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Corporation nor the names of the
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY SANDIA CORPORATION "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SANDIA CORPORATION OR THE
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Questions? Contact Christian R. Trott (crtrott@sandia.gov)
+ */
+/*! \libinternal \file
+ * \brief Declares gmx::mdspan
+ *
+ * \author Christian Trott <crtrott@sandia.gov>
+ * \author Ronan Keryell <ronan.keryell@xilinx.com>
+ * \author Carter Edwards <hedwards@nvidia.com>
+ * \author David Hollman <dshollm@sandia.gov>
+ * \author Christian Blau <cblau@gwdg.de>
+ * \ingroup mdspan
+ */
+#ifndef MDSPAN_MDSPAN_H
+#define MDSPAN_MDSPAN_H
+
+#include <array>
+#include <type_traits>
+
+#include "accessor_policy.h"
+#include "extents.h"
+#include "layouts.h"
+
+namespace gmx
+{
+
+/*! \libinternal \brief Multidimensional array indexing and memory access with flexible mapping and access model.
+ *
+ * \tparam ElementType Type of elemnt to be viewed
+ * \tparam Extents The dimensions of the multidimenisonal array to view.
+ * \tparam LayoutPolicy Describes is the memory layout of the multidimensional array; right by default.
+ * \tparam AccessorPolicy Describes memory access model.
+ */
+template<class ElementType, class Extents, class LayoutPolicy = layout_right, class AccessorPolicy = accessor_basic<ElementType> >
+class basic_mdspan
+{
+    public:
+        //! Expose type used to define the extents of the data.
+        using extents_type     = Extents;
+        //! Expose type used to define the layout of the data.
+        using layout_type      = LayoutPolicy;
+        //! Expose type used to define the memory access model of the data.
+        using accessor_type    = AccessorPolicy;
+        //! Expose type used to map multidimensional indices to one-dimensioal indices.
+        using mapping_type     = typename layout_type::template mapping<extents_type>;
+        //! Exposes the type of stored element.
+        using element_type     = typename accessor_type::element_type;
+        //! Expose the underlying type of the stored elements.
+        using value_type       = typename std::remove_cv<element_type>::type;
+        //! Expose the type used for indexing.
+        using index_type       = ptrdiff_t;
+        //! Expose type for index differences.
+        using difference_type  = ptrdiff_t;
+        //! Expose underlying pointer to data type.
+        using pointer          = typename accessor_type::pointer;
+        //! Expose reference to data type.
+        using reference        = typename accessor_type::reference;
+
+        //! Trivial constructor
+        constexpr basic_mdspan() noexcept : acc_(), map_(), ptr_() {}
+        //! Move constructor
+        constexpr basic_mdspan(basic_mdspan &&other) noexcept = default;
+        //! copy constructor
+        constexpr basic_mdspan(const basic_mdspan &other) noexcept = default;
+        //! Copy assignment
+        basic_mdspan &operator=(const basic_mdspan &other) noexcept = default;
+        //! Move assignment
+        basic_mdspan &operator=(basic_mdspan &&other) noexcept = default;
+
+        //! Copy constructor
+        template<class OtherElementType,
+                 class OtherExtents,
+                 class OtherLayoutPolicy,
+                 class OtherAccessor>
+        constexpr basic_mdspan(
+            const basic_mdspan<OtherElementType,
+                               OtherExtents,
+                               OtherLayoutPolicy,
+                               OtherAccessor> &rhs ) noexcept
+            : acc_( rhs.acc_ ),
+              map_( rhs.map_ ),
+              ptr_( rhs.ptr_ )
+        {}
+        //! Copy assignment constructor
+        template<class OtherElementType,
+                 class OtherExtents,
+                 class OtherLayoutPolicy,
+                 class OtherAccessor>
+        basic_mdspan &operator= (
+            const basic_mdspan<OtherElementType,
+                               OtherExtents,
+                               OtherLayoutPolicy,
+                               OtherAccessor> &rhs ) noexcept
+        { acc_ = rhs.acc_; map_ = rhs.map_; ptr_ = rhs.ptr_; return *this; }
+
+        /*!\brief Construct mdspan by setting the dynamic extents and pointer to data.
+         * \param[in] ptr Pointer to data to be accessed by this span
+         * \param[in] DynamicExtents
+         * \tparam IndexType index type to describe dynamic extents
+         */
+        template<class ... IndexType >
+        explicit constexpr basic_mdspan
+            ( pointer ptr, IndexType ... DynamicExtents ) noexcept
+            : acc_(accessor_type()), map_( extents_type(DynamicExtents ...) ), ptr_(ptr) {}
+        /*! \brief Construct from array describing dynamic extents.
+         * \param[in] ptr Pointer to data to be accessed by this span
+         * \param[in] dynamic_extents Array the size of dynamic extents.
+         */
+        constexpr basic_mdspan( pointer ptr, const std::array<ptrdiff_t, extents_type::rank_dynamic()> &dynamic_extents)
+            : acc_(accessor_type()), map_( extents_type(dynamic_extents)), ptr_(ptr) {}
+        /*! \brief Construct from pointer and mapping.
+         * \param[in] ptr Pointer to data to be accessed by this span
+         * \param[in] m Mapping from multidimenisonal indices to one-dimensional offset.
+         */
+        constexpr basic_mdspan( pointer ptr, const mapping_type &m ) noexcept
+            : acc_(accessor_type()), map_( m ), ptr_(ptr) {}
+        /*! \brief Construct with pointer, mapping and accessor.
+         * \param[in] ptr Pointer to data to be accessed by this span
+         * \param[in] m Mapping from multidimenisonal indices to one-dimensional offset.
+         * \param[in] a Accessor implementing memory access model.
+         */
+        constexpr basic_mdspan( pointer ptr, const mapping_type &m, const accessor_type &a ) noexcept
+            : acc_(a), map_( m ), ptr_(ptr) {}
+
+        /*! \brief Brace operator to access multidimenisonal array element.
+         * \param[in] indices The multidimensional indices of the object.
+         * Requires rank() == sizeof...(IndexType). Slicing is implemented via sub_span.
+         * \returns reference to element at indices.
+         */
+        template<class... IndexType >
+        constexpr typename std::enable_if<sizeof ... (IndexType) == extents_type::rank(), reference>::type
+        operator()( IndexType... indices) const noexcept
+        { return acc_.access( ptr_, map_( indices ... ) ); }
+        /*! \brief Canonical bracket operator for one-dimensional arrays.
+         * Allows mdspan to act like array in one-dimension.
+         * Enabled only when rank==1.
+         * \param[in] i one-dimensional index
+         * \returns reference to element stored at position i
+         */
+        template<class IndexType>
+        constexpr typename std::enable_if<std::is_integral<IndexType>::value && 1 == extents_type::rank(), reference>::type
+        operator[]( const IndexType &i ) const noexcept
+        { return acc_.access( ptr_, map_(i) ); }
+        //! Report the rank.
+        static constexpr int rank() noexcept
+        { return extents_type::rank(); }
+        //! Report the dynamic rank.
+        static constexpr int rank_dynamic() noexcept
+        { return extents_type::rank_dynamic(); }
+        /*! \brief Return the static extent.
+         * \param[in] k dimension to query for static extent
+         * \returns static extent along specified dimension
+         */
+        constexpr index_type static_extent( size_t k ) const noexcept
+        { return map_.extents().static_extent( k ); }
+
+        /*! \brief Return the extent.
+         * \param[in] k dimension to query for extent
+         * \returns extent along specified dimension
+         */
+        constexpr index_type extent( int k ) const noexcept
+        { return map_.extents().extent( k ); }
+
+        //! Return all extents
+        constexpr const extents_type &extents() const noexcept
+        { return map_.extents(); }
+        //! Report if mappings for this basic_span is always unique.
+        static constexpr bool is_always_unique()     noexcept { return mapping_type::is_always_unique(); }
+        //! Report if mapping for this basic_span is always strided
+        static constexpr bool is_always_strided()    noexcept { return mapping_type::is_always_strided(); }
+        //! Report if mapping for this basic_span is always is_contiguous
+        static constexpr bool is_always_contiguous() noexcept { return mapping_type::is_always_contiguous(); }
+        //! Report if the currently applied map is unique
+        constexpr bool is_unique() const noexcept  { return map_.is_unique(); }
+        //! Report if the currently applied map is strided
+        constexpr bool is_strided() const noexcept { return map_.is_strided(); }
+        //! Report if the currently applied map is contiguous
+        constexpr bool is_contiguous() const noexcept {return map_.is_contiguous(); }
+        //! Report stride along a specific rank.
+        constexpr index_type stride( size_t r ) const noexcept
+        { return map_.stride(r); }
+        //! Return the currently applied mapping.
+        constexpr mapping_type mapping() const noexcept { return map_; }
+        //! Return the memory access model.
+        constexpr accessor_type accessor() const noexcept { return acc_; }
+        //! Return pointer to underlying data
+        constexpr pointer data() const noexcept { return ptr_; }
+    private:
+        //! The memory access model
+        accessor_type acc_;
+        //! The transformation from multidimenisonal index to memory offset.
+        mapping_type  map_;
+        //! Memory location handle
+        pointer       ptr_;
+};
+
+//! basic_mdspan with wrapped indices, basic_accessor policiy and right-aligned  memory layout.
+template<class T, ptrdiff_t... Indices>
+using mdspan = basic_mdspan<T, extents<Indices...>, layout_right, accessor_basic<T> >;
+
+}      // namespace gmx
+
+#endif /* end of include guard: MDSPAN_MDSPAN_H */
index 0167d957e5d87b32746cfcf7adf396e63a5b8bcb..1130a7a061a347da923f5338537699a77c6b3b5f 100644 (file)
@@ -36,4 +36,5 @@ gmx_add_unit_test(MDSpanTests mdspan-test
                   accessor_policy.cpp
                   extents.cpp
                   layouts.cpp
+                  mdspan.cpp
                  )
diff --git a/src/gromacs/mdspan/tests/mdspan.cpp b/src/gromacs/mdspan/tests/mdspan.cpp
new file mode 100644 (file)
index 0000000..bb98daa
--- /dev/null
@@ -0,0 +1,302 @@
+/*
+ * This file is part of the GROMACS molecular simulation package.
+ *
+ * Copyright (c) 2018, by the GROMACS development team, led by
+ * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
+ * and including many others, as listed in the AUTHORS file in the
+ * top-level source directory and at http://www.gromacs.org.
+ *
+ * GROMACS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ *
+ * GROMACS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GROMACS; if not, see
+ * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+ *
+ * If you want to redistribute modifications to GROMACS, please
+ * consider that scientific software is very special. Version
+ * control is crucial - bugs must be traceable. We will be happy to
+ * consider code for inclusion in the official distribution, but
+ * derived work must not be called official GROMACS. Details are found
+ * in the README & COPYING files - if they are missing, get the
+ * official version at http://www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the research papers on the package. Check out http://www.gromacs.org.
+ */
+/*
+ * This file is a modified version of original work of Sandia Corporation.
+ * In the spirit of the original code, this particular file can be distributed
+ * on the terms of Sandia Corporation.
+ */
+/*
+ *                         Kokkos v. 2.0
+ *               Copyright (2014) Sandia Corporation
+ *
+ * Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
+ * the U.S. Government retains certain rights in this software.
+ *
+ * Kokkos is licensed under 3-clause BSD terms of use:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Corporation nor the names of the
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY SANDIA CORPORATION "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SANDIA CORPORATION OR THE
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Questions? Contact Christian R. Trott (crtrott@sandia.gov)
+ */
+/*! \internal \file
+ * \brief Testing gmx::basic_mdspan.
+ *
+ * \author Christian Trott <crtrott@sandia.gov>
+ * \author Carter Edwards <hedwards@nvidia.com>
+ * \author David Hollman <dshollm@sandia.gov>
+ * \author Christian Blau <cblau@gwdg.de>
+ */
+#include "gmxpre.h"
+
+#include "gromacs/mdspan/mdspan.h"
+
+#include <cstdio>
+
+#include <gtest/gtest.h>
+
+namespace gmx
+{
+
+namespace
+{
+
+
+// Test basic_mdspan with a mixture of dynamic and static extents, as well as extent of one.
+// The dynamic extents will be set to 4 and 2 repsectively so that we'll tests
+// a multidimensional array of extent 5 x 4 x 3 x 2 x 1, i.e. 120 elements
+
+//! View on int data with mixed static and dynamic extents
+using mdspan_int   = basic_mdspan < int, extents<5, dynamic_extent, 3, dynamic_extent, 1>, layout_right, accessor_basic < int>>;
+
+TEST(MdSpanTest, MdSpanWrapsBasicMdSpanCorrectly)
+{
+    // Check that mdspan wraps basic_mdspan as expected
+    ::testing::StaticAssertTypeEq < mdspan_int, mdspan < int, 5, dynamic_extent, 3, dynamic_extent, 1>>();
+}
+
+//! View on float data with mixed static and dynamic extents
+using mdspan_float = basic_mdspan < float, extents<5, dynamic_extent, 3, dynamic_extent, 1>, layout_right, accessor_basic < float>>;
+//! Types to be tested
+using MdSpanTypes      = ::testing::Types < mdspan_int, mdspan_float>;
+
+template<class ElementType, class Mapping>
+struct fill_raw_data {
+    static void fill(ElementType* p, Mapping m)
+    {
+        typename Mapping::extents_type e = m.extents();
+        for (ptrdiff_t i0 = 0; i0 < e.extent(0); i0++)
+        {
+            for (ptrdiff_t i1 = 0; i1 < e.extent(1); i1++)
+            {
+                for (ptrdiff_t i2 = 0; i2 < e.extent(2); i2++)
+                {
+                    for (ptrdiff_t i3 = 0; i3 < e.extent(3); i3++)
+                    {
+                        for (ptrdiff_t i4 = 0; i4 < e.extent(4); i4++)
+                        {
+                            p[i0*m.stride(0)+i1*m.stride(1)+i2*m.stride(2)+i3*m.stride(3)+i4*m.stride(4)] =
+                                ElementType(i0*10000+i1*1000+i2*100+i3*10+i4);
+                        }
+                    }
+                }
+            }
+        }
+    }
+};
+template<class MDSPAN>
+struct MdSpanTest : public ::testing::Test
+{
+    using mdspan_type   = MDSPAN;
+    using element_type  = typename mdspan_type::element_type;
+    using extents_type  = typename mdspan_type::extents_type;
+    using mapping_type  = typename mdspan_type::mapping_type;
+    using accessor_type = typename mdspan_type::accessor_type;
+    using pointer_type  = typename mdspan_type::pointer;
+
+    mdspan_type               my_mdspan_extents;
+    mdspan_type               my_mdspan_array;
+    mdspan_type               my_mdspan_mapping;
+    mdspan_type               my_mdspan_map_acc;
+    mdspan_type               my_mdspan_copy;
+
+    std::vector<element_type> rawData;
+
+    template<class ... ED>
+    void SetUp(ED ... e)
+    {
+        mapping_type  map {extents_type(e ...)};
+        accessor_type acc;
+        rawData.resize(map.required_span_size());
+        fill_raw_data<element_type, mapping_type>::fill(rawData.data(), map);
+        pointer_type  p(rawData.data());
+
+        my_mdspan_array   = mdspan_type(p, std::array<ptrdiff_t, sizeof ... (ED)>({{e ...}}));
+        my_mdspan_mapping = mdspan_type(p, map);
+        my_mdspan_map_acc = mdspan_type(p, map, acc);
+        my_mdspan_extents = mdspan_type(p, e ...);
+        my_mdspan_copy    = my_mdspan_mapping;
+    }
+
+    void check_rank(ptrdiff_t r)
+    {
+        EXPECT_EQ(my_mdspan_mapping.rank(), r);
+        EXPECT_EQ(my_mdspan_map_acc.rank(), r);
+        EXPECT_EQ(my_mdspan_extents.rank(), r);
+        EXPECT_EQ(my_mdspan_copy.rank(), r);
+    }
+    void check_rank_dynamic(ptrdiff_t r)
+    {
+        EXPECT_EQ(my_mdspan_mapping.rank_dynamic(), r);
+        EXPECT_EQ(my_mdspan_map_acc.rank_dynamic(), r);
+        EXPECT_EQ(my_mdspan_extents.rank_dynamic(), r);
+        EXPECT_EQ(my_mdspan_copy.rank_dynamic(), r);
+    }
+    template<class ... E>
+    void check_extents(E ... e)
+    {
+        std::array<ptrdiff_t, extents_type::rank()> a {{e ...}};
+        for (size_t r = 0; r < extents_type::rank(); r++)
+        {
+            EXPECT_EQ(my_mdspan_mapping.extent(r), a[r]);
+            EXPECT_EQ(my_mdspan_map_acc.extent(r), a[r]);
+            EXPECT_EQ(my_mdspan_extents.extent(r), a[r]);
+            EXPECT_EQ(my_mdspan_copy.extent(r), a[r]);
+        }
+    }
+    template<class ... E>
+    void check_strides(E ... e)
+    {
+        std::array<ptrdiff_t, extents_type::rank()> a {{e ...}};
+        for (size_t r = 0; r < extents_type::rank(); r++)
+        {
+            EXPECT_EQ(my_mdspan_mapping.stride(r), a[r]);
+            EXPECT_EQ(my_mdspan_map_acc.stride(r), a[r]);
+            EXPECT_EQ(my_mdspan_extents.stride(r), a[r]);
+            EXPECT_EQ(my_mdspan_copy.stride(r), a[r]);
+        }
+    }
+
+    void check_properties_internal(mdspan_type my_mdspan, bool always_unique, bool always_contiguous, bool always_strided,
+                                   bool unique, bool contiguous, bool strided)
+    {
+        EXPECT_EQ(my_mdspan.is_always_unique(), always_unique);
+        EXPECT_EQ(my_mdspan.is_always_contiguous(), always_contiguous);
+        EXPECT_EQ(my_mdspan.is_always_strided(), always_strided);
+        EXPECT_EQ(my_mdspan.is_unique(), unique);
+        EXPECT_EQ(my_mdspan.is_contiguous(), contiguous);
+        EXPECT_EQ(my_mdspan.is_strided(), strided);
+    }
+
+    void check_properties(bool always_unique, bool always_contiguous, bool always_strided,
+                          bool unique, bool contiguous, bool strided)
+    {
+        check_properties_internal(my_mdspan_mapping, always_unique, always_contiguous, always_strided, unique, contiguous, strided);
+        check_properties_internal(my_mdspan_map_acc, always_unique, always_contiguous, always_strided, unique, contiguous, strided);
+        check_properties_internal(my_mdspan_extents, always_unique, always_contiguous, always_strided, unique, contiguous, strided);
+        check_properties_internal(my_mdspan_copy, always_unique, always_contiguous, always_strided, unique, contiguous, strided);
+    }
+
+    void check_operator()
+    {
+        extents_type e = my_mdspan_mapping.extents();
+        for (ptrdiff_t i0 = 0; i0 < e.extent(0); i0++)
+        {
+            for (ptrdiff_t i1 = 0; i1 < e.extent(1); i1++)
+            {
+                for (ptrdiff_t i2 = 0; i2 < e.extent(2); i2++)
+                {
+                    for (ptrdiff_t i3 = 0; i3 < e.extent(3); i3++)
+                    {
+                        for (ptrdiff_t i4 = 0; i4 < e.extent(4); i4++)
+                        {
+                            element_type value = i0*10000+i1*1000+i2*100+i3*10+i4;
+                            EXPECT_EQ(my_mdspan_mapping(i0, i1, i2, i3, i4), value);
+                            EXPECT_EQ(my_mdspan_map_acc(i0, i1, i2, i3, i4), value);
+                            EXPECT_EQ(my_mdspan_extents(i0, i1, i2, i3, i4), value);
+                            EXPECT_EQ(my_mdspan_copy   (i0, i1, i2, i3, i4), value);
+                        }
+                    }
+                }
+            }
+        }
+    }
+};
+
+TYPED_TEST_CASE(MdSpanTest, MdSpanTypes);
+
+TYPED_TEST(MdSpanTest, Rank) {
+    this->SetUp(4, 2);
+    this->check_rank(5);
+}
+
+TYPED_TEST(MdSpanTest, DynamicRank) {
+    this->SetUp(4, 2);
+    this->check_rank_dynamic(2);
+}
+
+TYPED_TEST(MdSpanTest, Extents) {
+    this->SetUp(4, 2);
+    this->check_extents(5, 4, 3, 2, 1);
+}
+
+TYPED_TEST(MdSpanTest, Strides) {
+    this->SetUp(4, 2);
+    this->check_strides(24, 6, 2, 1, 1);
+}
+
+TYPED_TEST(MdSpanTest, Properties) {
+    this->SetUp(4, 2);
+    const bool always_unique     = true;
+    const bool always_contiguous = true;
+    const bool always_strided    = true;
+    const bool unique            = true;
+    const bool contiguous        = true;
+    const bool strided           = true;
+    this->check_properties(always_unique, always_contiguous, always_strided, unique, contiguous, strided);
+}
+
+TYPED_TEST(MdSpanTest, Operator) {
+    this->SetUp(4, 2);
+    this->check_operator();
+}
+
+} // namespace
+
+} // namespace gmx