From 9dd2d9cf36d7af29b5681e44dcd88c5db4cc4842 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 23 Sep 2025 11:50:17 +0200 Subject: [PATCH 1/5] cmake: enable building against libc++. --- CMakeLists.txt | 4 ++++ cmake/HighFiveLibCXX.cmake | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 cmake/HighFiveLibCXX.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 079979e2..7a61dc09 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -69,6 +69,7 @@ option(HIGHFIVE_TEST_EIGEN "Enable testing Eigen" OFF) option(HIGHFIVE_TEST_OPENCV "Enable testing OpenCV" OFF) option(HIGHFIVE_TEST_XTENSOR "Enable testing xtensor" OFF) option(HIGHFIVE_TEST_HALF_FLOAT "Enable testing half-precision floats" OFF) +option(HIGHFIVE_USE_LIBCXX "Use libc++ for tests/examples (clang only)" OFF) set(HIGHFIVE_MAX_ERRORS 0 CACHE STRING "Maximum number of compiler errors.") option(HIGHFIVE_HAS_WERROR "Convert warnings to errors." OFF) @@ -175,6 +176,9 @@ if(HIGHFIVE_EXAMPLES OR HIGHFIVE_UNIT_TESTS) include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/HighFiveOptionalDependencies.cmake) endif() +# Include libc++ configuration for examples and tests +include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/HighFiveLibCXX.cmake) + if(HIGHFIVE_EXAMPLES) add_subdirectory(src/examples) endif() diff --git a/cmake/HighFiveLibCXX.cmake b/cmake/HighFiveLibCXX.cmake new file mode 100644 index 00000000..31fffe12 --- /dev/null +++ b/cmake/HighFiveLibCXX.cmake @@ -0,0 +1,20 @@ +# HighFiveLibCXX.cmake +# Sets up CMAKE_* variables to use libc++ with clang when HIGHFIVE_USE_LIBCXX is enabled + +if(HIGHFIVE_USE_LIBCXX AND CMAKE_CXX_COMPILER_ID MATCHES "Clang") + # Set compiler flags to use libc++ + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") + + # Set linker flags to use libc++ + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -stdlib=libc++") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -stdlib=libc++") + set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -stdlib=libc++") + + + # Add necessary compile definitions for libc++ + add_compile_definitions(_LIBCPP_VERSION) + + message(STATUS "HighFive: Using libc++ for tests (clang only)") +elseif(HIGHFIVE_USE_LIBCXX AND NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang") + message(WARNING "HIGHFIVE_USE_LIBCXX is enabled but compiler is not clang. libc++ is only supported with clang.") +endif() From 5e25e75699eccbb4828208073102d92799776879 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 17 Nov 2025 18:22:25 +0100 Subject: [PATCH 2/5] add: support for std::mdspan. Supports layout_{left,right,stride} and both accessors, i.e. default_accessor and aligned_accessor. The padded layouts should also work, but since they're not implemented in any released version of (any of) the standard library, we're not testing them (yet). Copies are avoided for row-major layouts (layout_right) and for one dimensional column-major layouts. --- .github/workflows/ci.yml | 47 +++++++ CMakeLists.txt | 12 ++ cmake/HighFiveOptionalDependencies.cmake | 8 ++ include/highfive/mdspan.hpp | 167 +++++++++++++++++++++++ src/examples/CMakeLists.txt | 10 ++ src/examples/read_write_std_mdspan.cpp | 78 +++++++++++ tests/unit/CMakeLists.txt | 4 + tests/unit/data_generator.hpp | 150 ++++++++++++++++++++ tests/unit/supported_types.hpp | 58 ++++++++ 9 files changed, 534 insertions(+) create mode 100644 include/highfive/mdspan.hpp create mode 100644 src/examples/read_write_std_mdspan.cpp diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ac0f7aef..cfffd6ca 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -194,6 +194,53 @@ jobs: working-directory: ${{github.workspace}}/build/src/examples run: $GITHUB_WORKSPACE/.github/run_examples.sh + # Job testing clang with C++23, libc++ and MDSPAN + # ================================================ + Linux_Clang_CXX23_MDSPAN: + runs-on: ubuntu-24.04 + env: + CC: clang-21 + CXX: clang++-21 + + steps: + - uses: actions/checkout@v5 + with: + submodules: true + + - name: "Add LLVM repository" + run: | + wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo gpg --dearmor -o /usr/share/keyrings/llvm-snapshot.gpg + echo "deb [signed-by=/usr/share/keyrings/llvm-snapshot.gpg] http://apt.llvm.org/noble/ llvm-toolchain-noble-21 main" | sudo tee /etc/apt/sources.list.d/llvm.list + + - name: "Install libraries" + run: | + sudo apt-get -qq update + sudo apt-get -qq install clang-21 libc++-21-dev libc++abi-21-dev libhdf5-dev libsz2 ninja-build + + - name: Build + run: | + CMAKE_OPTIONS=( + -GNinja + -DCMAKE_CXX_STANDARD=23 + -DHIGHFIVE_USE_LIBCXX:BOOL=ON + -DHIGHFIVE_TEST_MDSPAN:BOOL=ON + ) + source $GITHUB_WORKSPACE/.github/build.sh + + - name: Test + working-directory: ${{github.workspace}}/build + run: | + ctest -j2 --output-on-failure -C $BUILD_TYPE + + - name: Test No HDF5 Diagnositics + working-directory: ${{github.workspace}}/build + run: | + ! ctest --verbose -C $BUILD_TYPE | grep HDF5-DIAG + + - name: Examples + working-directory: ${{github.workspace}}/build/src/examples + run: $GITHUB_WORKSPACE/.github/run_examples.sh + # Job running unit-test with sanitizers # ===================================== Linux_Sanitizers: diff --git a/CMakeLists.txt b/CMakeLists.txt index 7a61dc09..3c6b64cc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,11 +58,19 @@ else() set(HIGHFIVE_TEST_SPAN_DEFAULT Off) endif() +if (CMAKE_CXX_STANDARD GREATER_EQUAL 23) + include(CheckIncludeFileCXX) + CHECK_INCLUDE_FILE_CXX(mdspan HIGHFIVE_TEST_MDSPAN_DEFAULT) +else() + set(HIGHFIVE_TEST_MDSPAN_DEFAULT Off) +endif() + option(HIGHFIVE_UNIT_TESTS "Compile unit-tests" ${HIGHFIVE_EXTRAS_DEFAULT}) option(HIGHFIVE_EXAMPLES "Compile examples" ${HIGHFIVE_EXTRAS_DEFAULT}) option(HIGHFIVE_BUILD_DOCS "Build documentation" ${HIGHFIVE_EXTRAS_DEFAULT}) option(HIGHFIVE_TEST_SPAN "Enable testing std::span, requires C++20" ${HIGHFIVE_TEST_SPAN_DEFAULT}) +option(HIGHFIVE_TEST_MDSPAN "Enable testing std::mdspan, requires C++23 and libc++" ${HIGHFIVE_TEST_MDSPAN_DEFAULT}) option(HIGHFIVE_TEST_BOOST "Enable testing Boost features" OFF) option(HIGHFIVE_TEST_BOOST_SPAN "Additionally, enable testing `boost::span`" OFF) option(HIGHFIVE_TEST_EIGEN "Enable testing Eigen" OFF) @@ -92,6 +100,10 @@ if(CMAKE_CXX_STANDARD EQUAL 98 OR CMAKE_CXX_STANDARD LESS ${HIGHFIVE_CXX_STANDAR message(FATAL_ERROR "HighFive needs to be compiled with at least C++${HIGHFIVE_CXX_STANDARD_DEFAULT}") endif() +if(HIGHFIVE_TEST_MDSPAN AND CMAKE_CXX_STANDARD LESS 23) + message(FATAL_ERROR "HIGHFIVE_TEST_MDSPAN requires C++23 or newer, but CMAKE_CXX_STANDARD is ${CMAKE_CXX_STANDARD}") +endif() + add_compile_definitions(HIGHFIVE_CXX_STD=${CMAKE_CXX_STANDARD}) # HighFive diff --git a/cmake/HighFiveOptionalDependencies.cmake b/cmake/HighFiveOptionalDependencies.cmake index d8e6f0f1..f3c50941 100644 --- a/cmake/HighFiveOptionalDependencies.cmake +++ b/cmake/HighFiveOptionalDependencies.cmake @@ -52,6 +52,13 @@ if(NOT TARGET HighFiveSpanDependency) endif() endif() +if(NOT TARGET HighFiveMdspanDependency) + add_library(HighFiveMdspanDependency INTERFACE) + if(HIGHFIVE_TEST_MDSPAN) + target_compile_definitions(HighFiveMdspanDependency INTERFACE HIGHFIVE_TEST_MDSPAN=1) + endif() +endif() + if(NOT TARGET HighFiveOptionalDependencies) add_library(HighFiveOptionalDependencies INTERFACE) target_link_libraries(HighFiveOptionalDependencies INTERFACE @@ -60,5 +67,6 @@ if(NOT TARGET HighFiveOptionalDependencies) HighFiveXTensorDependency HighFiveOpenCVDependency HighFiveSpanDependency + HighFiveMdspanDependency ) endif() diff --git a/include/highfive/mdspan.hpp b/include/highfive/mdspan.hpp new file mode 100644 index 00000000..b289a6f0 --- /dev/null +++ b/include/highfive/mdspan.hpp @@ -0,0 +1,167 @@ +#pragma once + +#include "bits/H5Inspector_decl.hpp" +#include "H5Exception.hpp" + +#include +#include +#include +#include +#include +#include + +namespace HighFive { +namespace details { + +// Specialization for std::mdspan +template +struct inspector> { + using type = std::mdspan; + using value_type = typename type::element_type; + using base_type = typename inspector::base_type; + using hdf5_type = base_type; + using extents_type = typename type::extents_type; + using accessor_type = typename type::accessor_type; + + static constexpr size_t ndim = type::rank(); + static constexpr size_t min_ndim = ndim + inspector::min_ndim; + static constexpr size_t max_ndim = ndim + inspector::max_ndim; + + static constexpr bool is_trivially_copyable = + std::is_trivially_copyable::value && + inspector::is_trivially_nestable && + (std::is_same_v, accessor_type> +#ifdef __cpp_lib_aligned_accessor + || std::is_same_v, accessor_type> +#endif + ) && + (std::is_same_v || + (std::is_same_v && ndim == 1)); + static constexpr bool is_trivially_nestable = false; + + private: + using index_type = typename extents_type::index_type; + + // Helper to access the first element (at index 0 in all dimensions) + static auto get_first_element(const type& val) { + std::array indices{}; + return val[indices]; + } + + template + static auto data_impl(T& val) -> decltype(inspector::data(*val.data_handle())) { + if (!is_trivially_copyable) { + throw DataSetException("Invalid use of `inspector>::data`."); + } + + if (val.empty()) { + return nullptr; + } + + return inspector::data(*val.data_handle()); + } + + public: + static size_t getRank(const type& val) { + if (val.empty()) { + return min_ndim; + } + return ndim + inspector::getRank(get_first_element(val)); + } + + static std::vector getDimensions(const type& val) { + std::vector sizes; + sizes.reserve(ndim); + for (size_t r = 0; r < ndim; ++r) { + sizes.push_back(val.extent(r)); + } + if (!val.empty()) { + auto s = inspector::getDimensions(get_first_element(val)); + sizes.insert(sizes.end(), s.begin(), s.end()); + } + return sizes; + } + + static void prepare(type& val, const std::vector& dims) { + if (dims.size() < ndim) { + std::ostringstream os; + os << "Impossible to pair DataSet with " << dims.size() + << " dimensions into an mdspan with rank " << ndim << "."; + throw DataSpaceException(os.str()); + } + + // Check that dimensions match + for (size_t r = 0; r < ndim; ++r) { + if (dims[r] != val.extent(r)) { + std::ostringstream os; + os << "Mismatching dimensions for mdspan: expected " << val.extent(r) + << " for dimension " << r << ", but got " << dims[r] << "."; + throw DataSpaceException(os.str()); + } + } + } + + static hdf5_type* data(type& val) { + return data_impl(val); + } + + static const hdf5_type* data(const type& val) { + return data_impl(val); + } + + static void serialize(const type& val, const std::vector& dims, hdf5_type* m) { + auto subdims = std::vector(dims.begin() + ndim, dims.end()); + auto subsize = compute_total_size(subdims); + + std::array indices{}; + auto iterate = [&](auto& self, size_t dim) -> void { + if (dim == ndim) { + // Base case: serialize element + inspector::serialize(val[indices], subdims, m); + m += subsize; + } else { + // Recursive case: iterate over current dimension + const auto n = static_cast(val.extent(dim)); + for (indices[dim] = 0; indices[dim] < n; ++indices[dim]) { + self(self, dim + 1); + } + } + }; + + iterate(iterate, 0); + } + + static void unserialize(const hdf5_type* vec_align, + const std::vector& dims, + type& val) { + if (dims.size() < ndim) { + std::ostringstream os; + os << "Impossible to pair DataSet with " << dims.size() + << " dimensions into an mdspan with rank " << ndim << "."; + throw DataSpaceException(os.str()); + } + + auto subdims = std::vector(dims.begin() + ndim, dims.end()); + auto subsize = compute_total_size(subdims); + + std::array indices{}; + auto iterate = [&](auto& self, size_t dim) -> void { + if (dim == ndim) { + // Base case: unserialize element + inspector::unserialize(vec_align, subdims, val[indices]); + vec_align += subsize; + } else { + // Recursive case: iterate over current dimension + const auto n = static_cast(dims[dim]); + for (indices[dim] = 0; indices[dim] < n; ++indices[dim]) { + self(self, dim + 1); + } + } + }; + + iterate(iterate, 0); + } +}; + +} // namespace details +} // namespace HighFive diff --git a/src/examples/CMakeLists.txt b/src/examples/CMakeLists.txt index d55b10e0..a445b606 100644 --- a/src/examples/CMakeLists.txt +++ b/src/examples/CMakeLists.txt @@ -23,6 +23,10 @@ set(span_examples ${CMAKE_CURRENT_SOURCE_DIR}/read_write_std_span.cpp ) +set(mdspan_examples + ${CMAKE_CURRENT_SOURCE_DIR}/read_write_std_mdspan.cpp +) + set(easy_examples ${CMAKE_CURRENT_SOURCE_DIR}/easy_attribute.cpp ${CMAKE_CURRENT_SOURCE_DIR}/easy_dumpoptions.cpp @@ -85,6 +89,12 @@ if(HIGHFIVE_TEST_SPAN) endforeach() endif() +if(HIGHFIVE_TEST_MDSPAN) + foreach(example_source ${mdspan_examples}) + compile_example(${example_source} HighFiveFlags) + endforeach() +endif() + if(HIGHFIVE_TEST_BOOST) foreach(example_source ${boost_examples}) compile_example(${example_source} HighFiveFlags HighFiveBoostDependency) diff --git a/src/examples/read_write_std_mdspan.cpp b/src/examples/read_write_std_mdspan.cpp new file mode 100644 index 00000000..d0b4ed6e --- /dev/null +++ b/src/examples/read_write_std_mdspan.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (c), 2025, HighFive Developers + * + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + */ + +// This example demonstrates using `std::mdspan`. An `std::mdspan` is a +// multi-dimensional view over contiguous memory, similar to `std::span` but +// for multi-dimensional arrays. + +#include +#include +#include +#include + +#include + +#include + +int main(void) { + using namespace HighFive; + + std::string file_name = "read_write_mdspan.h5"; + std::string dataset_name = "array"; + + // Assume we have multi-dimensional data stored contiguously, e.g. in a + // vector. + constexpr size_t rows = 3; + constexpr size_t cols = 4; + std::vector values_row_major(rows * cols); + + // Fill the data + for (size_t i = 0; i < rows; ++i) { + for (size_t j = 0; j < cols; ++j) { + values_row_major[i * cols + j] = double(i * cols + j); + } + } + + // Create a 2D mdspan view over the contiguous memory. + auto view_row_major = std::mdspan(values_row_major.data(), std::extents{rows, cols}); + + { + File file(file_name, File::Truncate); + auto dataset = file.createDataSet(dataset_name, view_row_major); + } + + // Let's read from file. + { + File file(file_name, File::ReadOnly); + auto dataset = file.getDataSet(dataset_name); + + // Assume that memory was allocated by some means, e.g.: + auto dims = dataset.getDimensions(); + auto values_col_major = std::vector(dataset.getElementCount()); + + // Create a column-major mdspan view over the preallocated memory. + auto m = std::layout_left::mapping{std::extents{dims[0], dims[1]}}; + auto view_col_major = std::mdspan(values_col_major.data(), m); + + // ... now we can read into the preallocated memory: + dataset.read(view_col_major); + + // Check that the data was read correctly. + for (size_t i = 0; i < rows; ++i) { + for (size_t j = 0; j < cols; ++j) { + if (view_col_major[i, j] != view_row_major[i, j]) { + std::cerr << "Error: data was not read correctly." << std::endl; + return 1; + } + } + } + } + + return 0; +} diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 458ee8ab..60e9a464 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -75,6 +75,10 @@ foreach(PUBLIC_HEADER ${public_headers}) continue() endif() + if(PUBLIC_HEADER STREQUAL "highfive/mdspan.hpp" AND NOT HIGHFIVE_TEST_MDSPAN) + continue() + endif() + get_filename_component(CLASS_NAME ${PUBLIC_HEADER} NAME_WE) configure_file(tests_import_public_headers.cpp "tests_${CLASS_NAME}.cpp" @ONLY) add_executable("tests_include_${CLASS_NAME}" "${CMAKE_CURRENT_BINARY_DIR}/tests_${CLASS_NAME}.cpp") diff --git a/tests/unit/data_generator.hpp b/tests/unit/data_generator.hpp index 6da177b2..a40bae24 100644 --- a/tests/unit/data_generator.hpp +++ b/tests/unit/data_generator.hpp @@ -4,8 +4,11 @@ #include #include #include +#include #include #include +#include +#include #include @@ -29,6 +32,10 @@ #include #endif +#ifdef HIGHFIVE_TEST_MDSPAN +#include +#endif + namespace HighFive { namespace testing { @@ -601,6 +608,149 @@ struct ContainerTraits> }; +#endif + +// -- mdspan ------------------------------------------------------------------ +#ifdef HIGHFIVE_TEST_MDSPAN + +template +constexpr const size_t accessor_alignment = alignof(typename Acc::element_type); + +#ifdef __cpp_lib_aligned_accessor +template +constexpr const size_t accessor_alignment> = Align; +#endif + +template +struct ContainerTraits> { + using container_type = std::mdspan; + using value_type = typename container_type::element_type; + using base_type = typename ContainerTraits::base_type; + using extents_type = typename container_type::extents_type; + using layout_type = typename container_type::layout_type; + using accessor_type = typename container_type::accessor_type; + using index_type = typename extents_type::index_type; + + static constexpr bool is_view = true; + static constexpr size_t rank = container_type::rank(); + static constexpr bool is_layout_stride = std::is_same_v; + + static void set(container_type& array, + const std::vector& indices, + const base_type& value) { + auto local_indices = extract_local_indices(indices); + return ContainerTraits::set(array[local_indices], lstrip(indices, rank), value); + } + + static base_type get(const container_type& array, const std::vector& indices) { + auto local_indices = extract_local_indices(indices); + return ContainerTraits::get(array[local_indices], lstrip(indices, rank)); + } + + static void assign(container_type& dst, const container_type& src) { + dst = src; + } + + static std::array make_strides(const std::vector& dims) { + std::array strides{}; + if constexpr (rank > 0) { + // Start with padded left-most stride to ensure non-contiguous layout + strides[0] = 2; // 1 + padding + for (size_t i = 1; i < rank; ++i) { + strides[i] = strides[i - 1] * static_cast(dims[i - 1]); + } + } + return strides; + } + + static size_t container_size(const std::vector& local_dims) { + if constexpr (is_layout_stride && rank > 0) { + auto strides = make_strides(local_dims); + // Calculate max offset: sum of (extent[i]-1) * stride[i] for all i + size_t max_offset = 0; + for (size_t i = 0; i < rank; ++i) { + max_offset += (local_dims[i] - 1) * static_cast(strides[i]); + } + return max_offset + 1; + } else { + return flat_size(local_dims); + } + } + + static container_type make_container(value_type* ptr, const std::vector& local_dims) { + extents_type extents = make_extents(local_dims); + if constexpr (is_layout_stride) { + auto strides = make_strides(local_dims); + typename layout_type::mapping mapping(extents, strides); + return container_type(ptr, mapping); + } else { + return container_type(ptr, extents); + } + } + + static container_type allocate(const std::vector& dims) { + auto local_dims = std::vector(dims.begin(), dims.begin() + rank); + size_t n_elements = container_size(local_dims); + + size_t size = n_elements * sizeof(value_type); + size_t alignment = accessor_alignment; + // aligned_alloc requires size to be a multiple of alignment + size_t aligned_size = ((size + alignment - 1) / alignment) * alignment; + void* aligned_ptr = std::aligned_alloc(alignment, aligned_size); + if (!aligned_ptr) { + throw std::bad_alloc(); + } + value_type* ptr = static_cast(aligned_ptr); + + container_type array = make_container(ptr, local_dims); + + for (size_t i = 0; i < flat_size(local_dims); ++i) { + auto element = ContainerTraits::allocate(lstrip(dims, rank)); + set(array, unravel(i, local_dims), element); + } + + return array; + } + + static void deallocate(container_type& array, const std::vector& dims) { + auto local_dims = std::vector(dims.begin(), dims.begin() + rank); + size_t n_elements = flat_size(local_dims); + for (size_t i = 0; i < n_elements; ++i) { + auto indices = unravel(i, local_dims); + auto local_indices = extract_local_indices(indices); + ContainerTraits::deallocate(array[local_indices], lstrip(dims, rank)); + } + + std::free(array.data_handle()); + } + + static void sanitize_dims(std::vector& dims, size_t axis) { + // Check each extent and update dims if it's static + for (size_t r = 0; r < rank; ++r) { + if (extents_type::static_extent(r) != std::dynamic_extent) { + dims[axis + r] = static_cast(extents_type::static_extent(r)); + } + } + ContainerTraits::sanitize_dims(dims, axis + rank); + } + + private: + static std::array extract_local_indices(const std::vector& indices) { + std::array local_indices; + for (size_t i = 0; i < rank; ++i) { + local_indices[i] = static_cast(indices[i]); + } + return local_indices; + } + + static extents_type make_extents(const std::vector& dims) { + auto impl = [&dims](std::index_sequence) { + return extents_type{dims[Is]...}; + }; + return impl(std::make_index_sequence{}); + } +}; + #endif // -- XTensor ----------------------------------------------------------------- diff --git a/tests/unit/supported_types.hpp b/tests/unit/supported_types.hpp index ccda4a77..eaadc2a0 100644 --- a/tests/unit/supported_types.hpp +++ b/tests/unit/supported_types.hpp @@ -18,6 +18,10 @@ #include #endif +#ifdef HIGHFIVE_TEST_MDSPAN +#include +#endif + namespace HighFive { namespace testing { @@ -108,6 +112,25 @@ struct XArray { }; #endif +#ifdef HIGHFIVE_TEST_MDSPAN +template +struct STDMdspan { + template + using type = std::mdspan, Extents, Layout>; +}; + +#ifdef __cpp_lib_aligned_accessor +template +struct STDMdspanAligned { + template + using type = std::mdspan, + Extents, + Layout, + std::aligned_accessor, Alignment>>; +}; +#endif +#endif + template struct ContainerProduct; @@ -154,6 +177,7 @@ using some_scalar_types = typename ConcatenateTuples>, scalar_types_eigen>::type, typename ContainerProduct>, scalar_types_eigen>::type, typename ContainerProduct, scalar_types_eigen>::type, +#endif +#ifdef HIGHFIVE_TEST_MDSPAN + typename ContainerProduct, std::layout_left>, scalar_types_mdspan>::type, + typename ContainerProduct, std::layout_left>, scalar_types_mdspan>::type, + typename ContainerProduct, std::layout_left>, scalar_types_mdspan>::type, + typename ContainerProduct, std::layout_left>, scalar_types_mdspan>::type, + typename ContainerProduct, std::layout_left>>, scalar_types_mdspan>::type, + typename ContainerProduct, std::layout_left>>, scalar_types_mdspan>::type, + typename ContainerProduct, std::layout_right>, scalar_types_mdspan>::type, + typename ContainerProduct, std::layout_right>, scalar_types_mdspan>::type, + typename ContainerProduct, std::layout_right>, scalar_types_mdspan>::type, + typename ContainerProduct, std::layout_right>, scalar_types_mdspan>::type, + typename ContainerProduct, std::layout_right>>, scalar_types_mdspan>::type, + typename ContainerProduct, std::layout_right>>, scalar_types_mdspan>::type, + typename ContainerProduct, std::layout_stride>, scalar_types_mdspan>::type, + typename ContainerProduct, std::layout_stride>, scalar_types_mdspan>::type, + typename ContainerProduct, std::layout_stride>, scalar_types_mdspan>::type, + typename ContainerProduct, std::layout_stride>, scalar_types_mdspan>::type, + typename ContainerProduct, std::layout_stride>>, scalar_types_mdspan>::type, + typename ContainerProduct, std::layout_stride>>, scalar_types_mdspan>::type, +#ifdef __cpp_lib_aligned_accessor + typename ContainerProduct, std::layout_left>, scalar_types_mdspan>::type, + typename ContainerProduct, std::layout_left>, scalar_types_mdspan>::type, + typename ContainerProduct, std::layout_left>, scalar_types_mdspan>::type, + typename ContainerProduct, std::layout_left>, scalar_types_mdspan>::type, + typename ContainerProduct, std::layout_left>>, scalar_types_mdspan>::type, + typename ContainerProduct, std::layout_left>>, scalar_types_mdspan>::type, + typename ContainerProduct, std::layout_right>, scalar_types_mdspan>::type, + typename ContainerProduct, std::layout_right>, scalar_types_mdspan>::type, + typename ContainerProduct, std::layout_right>, scalar_types_mdspan>::type, + typename ContainerProduct, std::layout_right>, scalar_types_mdspan>::type, + typename ContainerProduct, std::layout_right>>, scalar_types_mdspan>::type, + typename ContainerProduct, std::layout_right>>, scalar_types_mdspan>::type, +#endif #endif typename ContainerProduct, all_scalar_types>::type, typename ContainerProduct>, some_scalar_types>::type, From 556b4a9da1262f36a5a45a9754c78ba647643681 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 18 Nov 2025 20:35:15 +0100 Subject: [PATCH 3/5] fix allocation bug --- tests/unit/data_generator.hpp | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/tests/unit/data_generator.hpp b/tests/unit/data_generator.hpp index a40bae24..165322ec 100644 --- a/tests/unit/data_generator.hpp +++ b/tests/unit/data_generator.hpp @@ -692,16 +692,7 @@ struct ContainerTraits(dims.begin(), dims.begin() + rank); size_t n_elements = container_size(local_dims); - size_t size = n_elements * sizeof(value_type); - size_t alignment = accessor_alignment; - // aligned_alloc requires size to be a multiple of alignment - size_t aligned_size = ((size + alignment - 1) / alignment) * alignment; - void* aligned_ptr = std::aligned_alloc(alignment, aligned_size); - if (!aligned_ptr) { - throw std::bad_alloc(); - } - value_type* ptr = static_cast(aligned_ptr); - + value_type* ptr = allocate_aligned_memory(n_elements); container_type array = make_container(ptr, local_dims); for (size_t i = 0; i < flat_size(local_dims); ++i) { @@ -735,6 +726,26 @@ struct ContainerTraits; + // aligned_alloc requires size to be a multiple of alignment + size_t aligned_size = ((size + alignment - 1) / alignment) * alignment; + void* aligned_ptr = std::aligned_alloc(alignment, aligned_size); + if (!aligned_ptr) { + throw std::runtime_error("Failed to allocate aligned memory: alignment=" + + std::to_string(alignment) + ", size=" + + std::to_string(size) + ", aligned_size=" + + std::to_string(aligned_size)); + } + return static_cast(aligned_ptr); + } + static std::array extract_local_indices(const std::vector& indices) { std::array local_indices; for (size_t i = 0; i < rank; ++i) { From 1daf9c0af36c0696511e4070fc495b81deefb74d Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 18 Nov 2025 20:36:08 +0100 Subject: [PATCH 4/5] formatting --- tests/unit/data_generator.hpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/unit/data_generator.hpp b/tests/unit/data_generator.hpp index 165322ec..8bf3d796 100644 --- a/tests/unit/data_generator.hpp +++ b/tests/unit/data_generator.hpp @@ -739,9 +739,8 @@ struct ContainerTraits