From 35908346b20b524d4d2d9f8f6220800c27595413 Mon Sep 17 00:00:00 2001
From: Stephane Del Pino <stephane.delpino44@gmail.com>
Date: Mon, 10 Jun 2024 22:21:56 +0200
Subject: [PATCH] git subrepo pull packages/HighFive

subrepo:
  subdir:   "packages/HighFive"
  merged:   "b74fabbea"
upstream:
  origin:   "git@github.com:BlueBrain/HighFive.git"
  branch:   "master"
  commit:   "b74fabbea"
git-subrepo:
  version:  "0.4.6"
  origin:   "git@github.com:ingydotnet/git-subrepo.git"
  commit:   "73a0129"
---
 CMakeLists.txt                                |   1 -
 packages/HighFive/.github/workflows/ci.yml    |  25 +-
 packages/HighFive/.gitrepo                    |   4 +-
 packages/HighFive/.travis.yml                 | 138 ---
 packages/HighFive/CMakeLists.txt              |  17 +-
 packages/HighFive/README.md                   |   2 +-
 .../cmake/HighFiveOptionalDependencies.cmake  |   8 +
 .../HighFive/cmake/HighFiveWarnings.cmake     |  44 +-
 packages/HighFive/doc/developer_guide.md      |   7 +-
 .../doxygen-awesome-css/doxygen-awesome.css   |  22 +-
 packages/HighFive/doc/migration_guide.md      | 114 ++-
 .../HighFive/include/highfive/H5Attribute.hpp |  35 +-
 packages/HighFive/include/highfive/H5File.hpp |   2 +-
 .../HighFive/include/highfive/H5Object.hpp    |  26 +-
 .../include/highfive/H5PropertyList.hpp       |   6 -
 .../HighFive/include/highfive/H5Selection.hpp |   8 +-
 .../highfive/bits/H5Attribute_misc.hpp        |  42 +-
 .../highfive/bits/H5Converter_misc.hpp        |   6 +-
 .../include/highfive/bits/H5DataType_misc.hpp |   6 +-
 .../highfive/bits/H5Dataspace_misc.hpp        |   7 +-
 .../include/highfive/bits/H5File_misc.hpp     |   2 +-
 .../highfive/bits/H5Inspector_decl.hpp        |  10 +-
 .../highfive/bits/H5Inspector_misc.hpp        | 189 ++--
 .../include/highfive/bits/H5Node_traits.hpp   |   3 -
 .../highfive/bits/H5Node_traits_misc.hpp      |  16 +-
 .../include/highfive/bits/H5Object_misc.hpp   |   6 -
 .../highfive/bits/H5PropertyList_misc.hpp     |   9 -
 .../highfive/bits/H5ReadWrite_misc.hpp        |  27 +-
 .../highfive/bits/H5Selection_misc.hpp        |   8 +-
 .../include/highfive/bits/H5Slice_traits.hpp  |  50 +-
 .../highfive/bits/H5Slice_traits_misc.hpp     |  45 +-
 .../bits/assert_compatible_spaces.hpp         |  29 +
 .../highfive/bits/compute_total_size.hpp      |  14 +
 .../highfive/bits/convert_size_vector.hpp     |  31 +
 .../include/highfive/bits/h5o_wrapper.hpp     |   9 +
 .../include/highfive/bits/squeeze.hpp         |  54 ++
 packages/HighFive/include/highfive/boost.hpp  |  45 +-
 packages/HighFive/include/highfive/eigen.hpp  |  12 +-
 .../include/highfive/experimental/opencv.hpp  | 149 ++++
 .../highfive/h5easy_bits/H5Easy_xtensor.hpp   |  10 +
 packages/HighFive/include/highfive/span.hpp   | 102 +++
 .../HighFive/include/highfive/xtensor.hpp     | 212 +++++
 packages/HighFive/src/examples/CMakeLists.txt |  12 +-
 .../src/examples/broadcasting_arrays.cpp      |  50 ++
 .../src/examples/read_write_std_span.cpp      |  56 ++
 packages/HighFive/tests/unit/CMakeLists.txt   |  59 +-
 .../HighFive/tests/unit/data_generator.hpp    | 159 +++-
 .../HighFive/tests/unit/supported_types.hpp   |  39 +
 .../HighFive/tests/unit/test_empty_arrays.cpp | 248 ++++++
 packages/HighFive/tests/unit/test_legacy.cpp  |  36 +
 packages/HighFive/tests/unit/test_opencv.cpp  |  59 ++
 packages/HighFive/tests/unit/test_string.cpp  | 351 ++++++++
 packages/HighFive/tests/unit/test_xtensor.cpp | 142 +++
 .../HighFive/tests/unit/tests_high_five.hpp   |   1 +
 .../tests/unit/tests_high_five_base.cpp       | 820 +++---------------
 .../tests/unit/tests_high_five_easy.cpp       |  38 +
 .../tests/unit/tests_high_five_multi_dims.cpp |   9 +
 57 files changed, 2442 insertions(+), 1189 deletions(-)
 delete mode 100644 packages/HighFive/.travis.yml
 create mode 100644 packages/HighFive/include/highfive/bits/assert_compatible_spaces.hpp
 create mode 100644 packages/HighFive/include/highfive/bits/compute_total_size.hpp
 create mode 100644 packages/HighFive/include/highfive/bits/convert_size_vector.hpp
 create mode 100644 packages/HighFive/include/highfive/bits/squeeze.hpp
 create mode 100644 packages/HighFive/include/highfive/experimental/opencv.hpp
 create mode 100644 packages/HighFive/include/highfive/span.hpp
 create mode 100644 packages/HighFive/include/highfive/xtensor.hpp
 create mode 100644 packages/HighFive/src/examples/broadcasting_arrays.cpp
 create mode 100644 packages/HighFive/src/examples/read_write_std_span.cpp
 create mode 100644 packages/HighFive/tests/unit/test_empty_arrays.cpp
 create mode 100644 packages/HighFive/tests/unit/test_opencv.cpp
 create mode 100644 packages/HighFive/tests/unit/test_string.cpp
 create mode 100644 packages/HighFive/tests/unit/test_xtensor.cpp

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 94da14ad5..51f020ba5 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -234,7 +234,6 @@ if (PUGS_ENABLE_HDF5 MATCHES "^(AUTO|ON)$")
     set(HIGHFIVE_UNIT_TESTS OFF)   # no unit tests
     set(HIGHFIVE_UNIT_TESTS OFF)   # no unit tests
     set(HIGHFIVE_EXAMPLES OFF)     # no examples
-    set(HIGHFIVE_PARALLEL_HDF5 ON) # activate parallel HDF5
     add_subdirectory(${PUGS_SOURCE_DIR}/packages/HighFive/)
     set(HIGHFIVE_TARGET HighFive)
   endif()
diff --git a/packages/HighFive/.github/workflows/ci.yml b/packages/HighFive/.github/workflows/ci.yml
index 49f415e82..66069123d 100644
--- a/packages/HighFive/.github/workflows/ci.yml
+++ b/packages/HighFive/.github/workflows/ci.yml
@@ -57,7 +57,7 @@ jobs:
               flags: '-DCMAKE_CXX_STANDARD=17 -DHIGHFIVE_TEST_BOOST:Bool=ON'
           - config:
               os: ubuntu-22.04
-              flags: '-DHIGHFIVE_TEST_BOOST=Off -DCMAKE_CXX_STANDARD=20'
+              flags: '-DHIGHFIVE_TEST_BOOST=Off -DCMAKE_CXX_STANDARD=20 -DHIGHFIVE_HAS_CONCEPTS=On'
 
     steps:
     - uses: actions/checkout@v3
@@ -76,7 +76,7 @@ jobs:
 
     - name: Build
       run: |
-        CMAKE_OPTIONS=(-DHIGHFIVE_PARALLEL_HDF5:BOOL=ON ${{ matrix.config.flags }})
+        CMAKE_OPTIONS=(-DHDF5_PREFER_PARALLEL:BOOL=ON ${{ matrix.config.flags }})
         source $GITHUB_WORKSPACE/.github/build.sh
 
     - name: Test
@@ -293,10 +293,13 @@ jobs:
     runs-on: ${{matrix.os}}
     strategy:
       matrix:
-        os: [ "macOS-12" ]
+        os: [ "macOS-14" ]
         cxxstd: ["14", "17", "20"]
 
         include:
+          - os: "macOS-12"
+            cxxstd: "14"
+
           - os: "macOS-13"
             cxxstd: "20"
 
@@ -316,8 +319,6 @@ jobs:
           -DHIGHFIVE_TEST_EIGEN:BOOL=ON
           -DHIGHFIVE_TEST_XTENSOR:BOOL=ON
           -DHIGHFIVE_BUILD_DOCS:BOOL=FALSE
-          -DHIGHFIVE_TEST_SINGLE_INCLUDES=ON
-          -DCMAKE_CXX_FLAGS="-coverage -O0"
           -DCMAKE_CXX_STANDARD=${{matrix.cxxstd}}
         )
         source $GITHUB_WORKSPACE/.github/build.sh
@@ -343,19 +344,9 @@ jobs:
     runs-on:  ${{matrix.os}}
     strategy:
       matrix:
-        os: [ "windows-2022"]
-        vs-toolset: [ "v141", "v143" ]
+        os: [ "windows-2019", "windows-2022"]
         cxxstd: ["14", "17", "20"]
 
-        include:
-          - os: "windows-2019"
-            vs-toolset: "v142"
-            cxxstd: "14"
-
-          - os: "windows-2019"
-            vs-toolset: "v142"
-            cxxstd: "17"
-
     steps:
     - uses: actions/checkout@v3
       with:
@@ -370,13 +361,11 @@ jobs:
       shell: bash -l {0}
       run: |
         CMAKE_OPTIONS=(
-          -T ${{matrix.vs-toolset}}
           -DCMAKE_CXX_STANDARD=${{matrix.cxxstd}}
           -DHIGHFIVE_UNIT_TESTS=ON
           -DHIGHFIVE_TEST_BOOST:BOOL=ON
           -DHIGHFIVE_TEST_EIGEN:BOOL=ON
           -DHIGHFIVE_TEST_XTENSOR:BOOL=ON
-          -DHIGHFIVE_TEST_SINGLE_INCLUDES=ON
         )
         source $GITHUB_WORKSPACE/.github/build.sh
 
diff --git a/packages/HighFive/.gitrepo b/packages/HighFive/.gitrepo
index 308a575dd..ba3061909 100644
--- a/packages/HighFive/.gitrepo
+++ b/packages/HighFive/.gitrepo
@@ -6,7 +6,7 @@
 [subrepo]
 	remote = git@github.com:BlueBrain/HighFive.git
 	branch = master
-	commit = 5e0204e272e4f71e3dd768287e400758f785527d
-	parent = 760bc7af37b9a29960a20602cfc91e35e22e24a6
+	commit = b74fabbea7bc4422a6891b2f5a27305b8dd8cd1b
+	parent = d64d966d026b0bc07529a0328b8607036ecc24d6
 	method = merge
 	cmdver = 0.4.6
diff --git a/packages/HighFive/.travis.yml b/packages/HighFive/.travis.yml
deleted file mode 100644
index bc5d34081..000000000
--- a/packages/HighFive/.travis.yml
+++ /dev/null
@@ -1,138 +0,0 @@
-# Adapted from various sources, including:
-# - Louis Dionne's Hana: https://github.com/ldionne/hana
-# - Paul Fultz II's FIT: https://github.com/pfultz2/Fit
-# - Eric Niebler's range-v3: https://github.com/ericniebler/range-v3
-# - Gabi Melman spdlog: https://github.com/gabime/spdlog
-
-sudo: required
-language: cpp
-
-addons: &gcc7
-  apt:
-    packages:
-      - g++-7
-      - libboost-all-dev
-      - libhdf5-openmpi-dev
-      - libeigen3-dev
-      - ninja-build
-    sources:
-      - ubuntu-toolchain-r-test
-
-matrix:
-  include:
-    # Older linux (trusty) with default gcc
-    # Install serial hdf5 + build serial
-    - os: linux
-      dist: trusty
-      env:
-        - HIGHFIVE_USE_XTENSOR=False
-        - HIGHFIVE_USE_OPENCV=False
-        - HIGHFIVE_PARALLEL_HDF5=False
-        - IS_BASE_ENVIRON=1
-      addons:
-        apt:
-          packages:
-            - libboost-all-dev
-            - libeigen3-dev
-            - libhdf5-serial-dev
-            - ninja-build
-
-    # Linux gcc-7
-    # Install parallel hdf5 + build parallel
-    - os: linux
-      dist: xenial
-      env:
-        - GCC_VERSION=7
-        - HIGHFIVE_USE_XTENSOR=True
-        - HIGHFIVE_USE_OPENCV=False
-        - HIGHFIVE_PARALLEL_HDF5=True
-      addons: *gcc7
-
-    # Mac OSX XCode 10
-    - os: osx
-      osx_image: xcode10.3
-      env:
-        - HIGHFIVE_USE_XTENSOR=True
-        - HIGHFIVE_USE_OPENCV=True
-        - HIGHFIVE_PARALLEL_HDF5=False
-
-    # Windows
-    - os: windows
-      env:
-        - HIGHFIVE_USE_XTENSOR=True
-        - HIGHFIVE_USE_OPENCV=True
-        - HIGHFIVE_PARALLEL_HDF5=False
-
-env:
-  global:
-    - MINCONDA_VERSION="latest"
-    - MINCONDA_LINUX="Linux-x86_64"
-    - MINCONDA_OSX="MacOSX-x86_64"
-
-install:
-  - export HOMEBREW_NO_AUTO_UPDATE=1  # for reproducibility, dont autoupdate
-
-  - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
-      MINCONDA_OS=$MINCONDA_LINUX;
-    elif [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
-      if [ "$BREW_USE_LATEST" ]; then
-        brew update;
-        brew install hdf5; brew upgrade hdf5;
-      fi;
-      brew install boost hdf5 eigen ninja;
-      MINCONDA_OS=$MINCONDA_OSX;
-    fi
-
-  - if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then
-      export CMAKE_GENERATOR="Visual Studio 15 2017 Win64" ;
-      export TESTS_TARGET="RUN_TESTS";
-      choco install --yes miniconda3 ;
-      source C:/Tools/miniconda3/Scripts/activate ;
-    else
-      export CMAKE_GENERATOR="Ninja" ;
-      export TESTS_TARGET="test";
-      wget "http://repo.continuum.io/miniconda/Miniconda3-$MINCONDA_VERSION-$MINCONDA_OS.sh" -O miniconda.sh;
-      bash miniconda.sh -b -p $HOME/miniconda ;
-      source $HOME/miniconda/bin/activate;
-      hash -r ;
-    fi
-  - conda config --set always_yes yes --set changeps1 no
-  - conda update -q conda
-  - conda install -c conda-forge mamba
-  - if [[ "$HIGHFIVE_USE_XTENSOR" == "True" ]]; then
-      mamba install -c conda-forge xtl xsimd xtensor;
-    fi
-  - if [[ "$HIGHFIVE_USE_OPENCV" == "True" ]]; then
-      mamba install -c conda-forge libopencv opencv;
-    fi
-  - if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then
-      mamba install -c conda-forge boost-cpp hdf5 eigen;
-    fi
-
-before_script:
-  - if [ -n "$GCC_VERSION" ]; then export CXX="g++-${GCC_VERSION}" CC="gcc-${GCC_VERSION}"; fi
-  - if [ -n "$CLANG_VERSION" ]; then export CXX="clang++-${CLANG_VERSION}" CC="clang-${CLANG_VERSION}"; fi
-  - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then export CXX="clang++" CC="clang"; fi
-  - which $CXX
-  - which $CC
-  - $CXX --version
-  - cmake --version
-
-script:
-  - cd ${TRAVIS_BUILD_DIR}
-  - mkdir -p build && pushd build
-  - >
-    cmake --warn-uninitialized --debug-output
-    -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON
-    -DHIGHFIVE_TEST_SINGLE_INCLUDES:BOOL=ON
-    -DHIGHFIVE_PARALLEL_HDF5:BOOL=${HIGHFIVE_PARALLEL_HDF5}
-    -DHIGHFIVE_USE_EIGEN:BOOL=ON
-    -DHIGHFIVE_USE_XTENSOR:BOOL=${HIGHFIVE_USE_XTENSOR}
-    -DHIGHFIVE_USE_OPENCV:BOOL=${HIGHFIVE_USE_OPENCV}
-    -G "${CMAKE_GENERATOR}" ../
-  - cmake --build .
-  - CTEST_OUTPUT_ON_FAILURE=1 cmake --build . --target ${TESTS_TARGET}
-  - popd
-  - if [ $IS_BASE_ENVIRON ]; then
-      bash tests/test_project_integration.sh;
-    fi
diff --git a/packages/HighFive/CMakeLists.txt b/packages/HighFive/CMakeLists.txt
index 7060fe713..2358e4172 100644
--- a/packages/HighFive/CMakeLists.txt
+++ b/packages/HighFive/CMakeLists.txt
@@ -47,10 +47,18 @@ else()
   set(HIGHFIVE_EXTRAS_DEFAULT OFF)
 endif()
 
+if (CMAKE_CXX_STANDARD GREATER_EQUAL 20)
+  include(CheckIncludeFileCXX)
+  CHECK_INCLUDE_FILE_CXX(span HIGHFIVE_TEST_SPAN_DEFAULT)
+else()
+  set(HIGHFIVE_TEST_SPAN_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 std::span testing, requires C++20" ${HIGHFIVE_TEST_SPAN_DEFAULT})
 option(HIGHFIVE_TEST_BOOST "Enable Boost testing" OFF)
 option(HIGHFIVE_TEST_EIGEN "Enable Eigen testing" OFF)
 option(HIGHFIVE_TEST_OPENCV "Enable OpenCV testing" OFF)
@@ -60,7 +68,7 @@ option(HIGHFIVE_TEST_HALF_FLOAT "Enable half-precision floats" OFF)
 # TODO remove entirely.
 option(HIGHFIVE_HAS_CONCEPTS "Print readable compiler errors w/ C++20 concepts" OFF)
 
-set(HIGHFIVE_MAX_ERROR 0 "Maximum number of compiler errors.")
+set(HIGHFIVE_MAX_ERRORS 0 CACHE STRING "Maximum number of compiler errors.")
 option(HIGHFIVE_HAS_WERROR "Convert warnings to errors." OFF)
 option(HIGHFIVE_GLIBCXX_ASSERTIONS "Enable bounds check for STL." OFF)
 # TODO these some magic to get a drop down menu in ccmake
@@ -69,15 +77,16 @@ mark_as_advanced(HIGHFIVE_SANITIZER)
 
 # Check compiler cxx_std requirements
 # -----------------------------------
+set(HIGHFIVE_CXX_STANDARD_DEFAULT 14)
 
 if(NOT DEFINED CMAKE_CXX_STANDARD)
-    set(CMAKE_CXX_STANDARD 14)
+    set(CMAKE_CXX_STANDARD ${HIGHFIVE_CXX_STANDARD_DEFAULT})
     set(CMAKE_CXX_STANDARD_REQUIRED ON)
     set(CMAKE_CXX_EXTENSIONS OFF)
 endif()
 
-if(CMAKE_CXX_STANDARD EQUAL 98 OR CMAKE_CXX_STANDARD LESS 14)
-    message(FATAL_ERROR "HighFive needs to be compiled with at least C++14")
+if(CMAKE_CXX_STANDARD EQUAL 98 OR CMAKE_CXX_STANDARD LESS ${HIGHFIVE_CXX_STANDARD_DEFAULT})
+  message(FATAL_ERROR "HighFive needs to be compiled with at least C++${HIGHFIVE_CXX_STANDARD_DEFAULT}")
 endif()
 
 add_compile_definitions(HIGHFIVE_CXX_STD=${CMAKE_CXX_STANDARD})
diff --git a/packages/HighFive/README.md b/packages/HighFive/README.md
index 8e04eb13c..b8d71b357 100644
--- a/packages/HighFive/README.md
+++ b/packages/HighFive/README.md
@@ -48,7 +48,7 @@ It integrates nicely with other CMake projects by defining (and exporting) a Hig
 
 ### Known flaws
 - HighFive is not thread-safe. At best it has the same limitations as the HDF5 library. However, HighFive objects modify their members without protecting these writes. Users have reported that HighFive is not thread-safe even when using the threadsafe HDF5 library, e.g., https://github.com/BlueBrain/HighFive/discussions/675.
-- Eigen support in core HighFive is broken. See https://github.com/BlueBrain/HighFive/issues/532. H5Easy is not
+- Eigen support in core HighFive was broken until v3.0. See https://github.com/BlueBrain/HighFive/issues/532. H5Easy was not
   affected.
 - The support of fixed length strings isn't ideal.
 
diff --git a/packages/HighFive/cmake/HighFiveOptionalDependencies.cmake b/packages/HighFive/cmake/HighFiveOptionalDependencies.cmake
index 1b27edd10..861b80641 100644
--- a/packages/HighFive/cmake/HighFiveOptionalDependencies.cmake
+++ b/packages/HighFive/cmake/HighFiveOptionalDependencies.cmake
@@ -37,6 +37,13 @@ if(NOT TARGET HighFiveOpenCVDependency)
   endif()
 endif()
 
+if(NOT TARGET HighFiveSpanDependency)
+  add_library(HighFiveSpanDependency INTERFACE)
+  if(HIGHFIVE_TEST_SPAN)
+    target_compile_definitions(HighFiveSpanDependency INTERFACE HIGHFIVE_TEST_SPAN=1)
+  endif()
+endif()
+
 if(NOT TARGET HighFiveOptionalDependencies)
   add_library(HighFiveOptionalDependencies INTERFACE)
   target_link_libraries(HighFiveOptionalDependencies INTERFACE
@@ -44,5 +51,6 @@ if(NOT TARGET HighFiveOptionalDependencies)
     HighFiveEigenDependency
     HighFiveXTensorDependency
     HighFiveOpenCVDependency
+    HighFiveSpanDependency
   )
 endif()
diff --git a/packages/HighFive/cmake/HighFiveWarnings.cmake b/packages/HighFive/cmake/HighFiveWarnings.cmake
index 3f569d5d5..a1dee19dc 100644
--- a/packages/HighFive/cmake/HighFiveWarnings.cmake
+++ b/packages/HighFive/cmake/HighFiveWarnings.cmake
@@ -24,33 +24,35 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "Clang"
             -Wconversion
             -Wsign-conversion
     )
+endif()
+
+if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU")
+    target_compile_options(HighFiveWarnings
+        INTERFACE
+            -Wpedantic
+            -Wcast-align
+            -Wdouble-promotion
+    )
+
+    target_compile_options(HighFiveWarnings
+        INTERFACE
+            -ftemplate-backtrace-limit=0
+    )
 
-    if(NOT CMAKE_CXX_COMPILER_ID MATCHES "Intel")
+    if(HIGHFIVE_HAS_WERROR)
         target_compile_options(HighFiveWarnings
             INTERFACE
-                -Wpedantic
-                -Wcast-align
-                -Wdouble-promotion
+                -Werror
+                -Wno-error=deprecated-declarations
         )
+    endif()
+endif()
 
-        target_compile_options(HighFiveWarnings
+if(CMAKE_CXX_COMPILER_ID MATCHES "GNU")
+    if(HIGHFIVE_MAX_ERRORS)
+        target_compile_options(HighFiveFlags
             INTERFACE
-                -ftemplate-backtrace-limit=0
+            -fmax-errors=${HIGHFIVE_MAX_ERRORS}
         )
-
-      if(HIGHFIVE_MAX_ERRORS)
-          target_compile_options(HighFiveFlags
-              INTERFACE
-              -fmax-errors=${HIGHFIVE_MAX_ERRORS}
-          )
-      endif()
-
-      if(HIGHFIVE_HAS_WERROR)
-          target_compile_options(HighFiveWarnings
-              INTERFACE
-                  -Werror
-                  -Wno-error=deprecated-declarations
-          )
-      endif()
     endif()
 endif()
diff --git a/packages/HighFive/doc/developer_guide.md b/packages/HighFive/doc/developer_guide.md
index 13e360fc3..0f7af3e01 100644
--- a/packages/HighFive/doc/developer_guide.md
+++ b/packages/HighFive/doc/developer_guide.md
@@ -25,7 +25,7 @@ ctest --test-dir build
 
 You might want to add:
 * `-DHIGHFIVE_TEST_BOOST=On` or other optional dependencies on,
-* `-DHIGHFIVE_MAX_ERROR=3` to only show the first three errors.
+* `-DHIGHFIVE_MAX_ERRORS=3` to only show the first three errors.
 
 Generic CMake reminders:
 * `-DCMAKE_INSTALL_PREFIX` defines where HighFive will be installed,
@@ -195,6 +195,9 @@ Write-read cycles for scalar values should be implemented in
 Unit-tests related to checking that `DataType` API, go in
 `tests/unit/tests_high_data_type.cpp`.
 
+#### Empty Arrays
+Check related to empty arrays to in `tests/unit/test_empty_arrays.cpp`.
+
 #### Selections
 Anything selection related goes in `tests/unit/test_high_five_selection.cpp`.
 This includes things like `ElementSet` and `HyperSlab`.
@@ -204,7 +207,7 @@ Regular write-read cycles for strings are performed along with the other types,
 see above. This should cover compatibility of `std::string` with all
 containers. However, additional testing is required, e.g. character set,
 padding, fixed vs. variable length. These all go in
-`tests/unit/test_high_five_string.cpp`.
+`tests/unit/test_string.cpp`.
 
 #### Specific Tests For Optional Containers
 If containers, e.g. `Eigen::Matrix` require special checks those go in files
diff --git a/packages/HighFive/doc/doxygen-awesome-css/doxygen-awesome.css b/packages/HighFive/doc/doxygen-awesome-css/doxygen-awesome.css
index a44945b36..a2715e268 100644
--- a/packages/HighFive/doc/doxygen-awesome-css/doxygen-awesome.css
+++ b/packages/HighFive/doc/doxygen-awesome-css/doxygen-awesome.css
@@ -1046,7 +1046,7 @@ blockquote::after {
 blockquote p {
     margin: var(--spacing-small) 0 var(--spacing-medium) 0;
 }
-.paramname {
+.paramname, .paramname em {
     font-weight: 600;
     color: var(--primary-dark-color);
 }
@@ -1096,7 +1096,7 @@ div.contents .toc {
     border: 0;
     border-left: 1px solid var(--separator-color);
     border-radius: 0;
-    background-color: transparent;
+    background-color: var(--page-background-color);
     box-shadow: none;
     position: sticky;
     top: var(--toc-sticky-top);
@@ -1988,14 +1988,16 @@ hr {
 }
 
 .contents hr {
-    box-shadow: 100px 0 0 var(--separator-color),
-                -100px 0 0 var(--separator-color),
-                500px 0 0 var(--separator-color),
-                -500px 0 0 var(--separator-color),
-                1500px 0 0 var(--separator-color),
-                -1500px 0 0 var(--separator-color),
-                2000px 0 0 var(--separator-color),
-                -2000px 0 0 var(--separator-color);
+    box-shadow: 100px 0 var(--separator-color),
+                -100px 0 var(--separator-color),
+                500px 0 var(--separator-color),
+                -500px 0 var(--separator-color),
+                900px 0 var(--separator-color),
+                -900px 0 var(--separator-color),
+                1400px 0 var(--separator-color),
+                -1400px 0 var(--separator-color),
+                1900px 0 var(--separator-color),
+                -1900px 0 var(--separator-color);       
 }
 
 .contents img, .contents .center, .contents center, .contents div.image object {
diff --git a/packages/HighFive/doc/migration_guide.md b/packages/HighFive/doc/migration_guide.md
index 2ffe9e257..ee72e52c7 100644
--- a/packages/HighFive/doc/migration_guide.md
+++ b/packages/HighFive/doc/migration_guide.md
@@ -15,7 +15,6 @@ replaced with an `std::vector<std::string>` (for example).
 If desired one can silence warnings by replacing `FixedLenStringArray` with
 `deprecated::FixedLenStringArray`.
 
-
 ## Deprecation of `read(T*, ...)`.
 A "raw read" is when the user allocates sufficient bytes and provides HighFive
 with the pointer to the first byte. "Regular reads" take a detour via the
@@ -40,7 +39,7 @@ dset.read(x);
 which is fine because is a contiguous sequence of doubles. It's equivalent to
 following `v3` code:
 ```
-double x[2][3];
+double x[n][m];
 dset.read_raw((double*) x);
 ```
 
@@ -48,11 +47,11 @@ dset.read_raw((double*) x);
 We consider the example above to be accidentally using a raw read, when it
 could be performing a regular read. We suggest to not change the above, i.e.
 ```
-double x[2][3];
+double x[n][m];
 dset.read(x);
 ```
 continues to be correct in `v3` and can check that the dimensions match. The
-inspector recognizes `double[2][3]` as a contiguous array of doubles.
+inspector recognizes `double[n][m]` as a contiguous array of doubles.
 Therefore, it'll use the shallow-copy buffer and avoid the any additional
 allocations or copies.
 
@@ -61,11 +60,48 @@ When genuinely performing a "raw read", one must replace `read` with
 `read_raw`. For example:
 
 ```
-double* x = malloc(2*3 * sizeof(double));
+double* x = malloc(n*m * sizeof(double));
 dset.read_raw(x);
 ```
 is correct in `v3`.
 
+## Change for `T**`, `T***`, etc.
+*The immediately preceding section is likely relevant.*
+
+In `v2` raw pointers could be used to indicate dimensionality. For example:
+```
+double* x = malloc(n*m * sizeof(double));
+auto dset = file.createDataSet("foo", DataSpace({n, m}), ...);
+
+dset.write((double**) x);
+dset.read((double**) x);
+```
+was valid and would write the flat array `x` into the two-dimensional dataset
+`"foo"`. This must be modernized as follows:
+```
+double* x = malloc(n*m * sizeof(double));
+auto dset = file.createDataSet("foo", DataSpace({n, m}), ...);
+
+dset.write_raw(x);
+dset.read_raw(x);
+```
+
+In `v3` the type `T**` will refer a pointer to a pointer (as usual). The
+following:
+```
+size_t n = 2, m = 3;
+double** x = malloc(n * sizeof(double*));
+for(size_t i = 0; i < n; ++i) {
+  x[i] = malloc(m * sizeof(double));
+}
+
+auto dset = file.createDataSet("foo", DataSpace({n, m}), ...);
+dset.write(x);
+dset.read(x);
+```
+is correct in `v3` but would probably segfault in `v2`.
+
+
 ## Reworked CMake
 In `v3` we completely rewrote the CMake code of HighFive. Since HighFive is a
 header only library, it needs to perform two tasks:
@@ -132,3 +168,71 @@ We felt that the savings in typing effort weren't worth introducing the concept
 of a "file driver". Removing the concept hopefully makes it easier to add a
 better abstraction for the handling of the property lists, when we discover
 such an abstraction.
+
+## Removal of broadcasting
+HighFive v2 had a feature that a dataset (or attribute) of shape `[n, 1]` could
+be read into a one-dimensional array automatically.
+
+The feature is prone to accidentally not failing. Consider an array that shape
+`[n, m]` and in general both `n, m > 0`. Hence, one should always be reading
+into a two-dimensional array, even if `n == 1` or `m == 1`. However, due to
+broadcasting, if one of the dimensions (accidentally) happens to be one, then
+the checks wont fails. This isn't a bug, however, it can hide a bug. For
+example if the test happen to use `[n, 1]` datasets and a one-dimensional
+array.
+
+Broadcasting in HighFive was different from broadcasting in NumPy. For reading
+into one-dimensional data HighFive supports stripping all dimensions that are
+not `1`. When extending the feature to multi-dimensional arrays it gets tricky.
+We can't strip from both the front and back. If we allow stripping from both
+ends, arrays such as `[1, n, m]` read into `[n, m]` if `m > 1` but into `[1,
+n]` (instead of `[n, 1]`) if (coincidentally) `m == 1`. For HighFive because
+avoiding being forced to read `[n, 1]` into `std::vector<std::vector<T>>` is
+more important than `[1, n]`.  Flattening the former requires copying
+everything while the latter can be made flat by just accessing the first value.
+Therefore, HighFive had a preference to strip from the right, while NumPy adds
+`1`s to the front/left of the shape.
+
+In `v3` we've removed broadcasting. Instead users must use one of the two
+alternatives: squeezing and reshaping. The examples show will use datasets and
+reading, but it works the same for attributes and writing.
+
+### Squeezing
+Often we know that the `k`th dimension is `1`, e.g. a column is `[n, 1]` and a
+row is `[1, m]`. In this case it's convenient to state, remove dimension `k`.
+The syntax to simultaneously remove the dimensions `{0, 2}` is:
+
+```
+dset.squeezeMemSpace({0, 2}).read(array);
+```
+Which will read a dataset with dimensions `[1, n, 1]` into an array of shape
+`[n]`.
+
+### Reshape
+Sometimes it's easier to state what the new shape must be. For this we have the
+syntax:
+```
+dset.reshapeMemSpace(dims).read(array);
+```
+To declare that `array` should have dimensions `dims` even if
+`dset.getDimensions()` is something different.
+
+Example:
+```
+dset.reshapeMemSpace({dset.getElementCount()}).read(array);
+```
+to read into a one-dimensional array.
+
+### Scalars
+There's a safe case that seems needlessly strict to enforce: if the dataset is
+a multi-dimensional array with one element one should be able to read into
+(write from) a scalar.
+
+The reverse, i.e. reading a scalar value in the HDF5 file into a
+multi-dimensional array isn't supported, because if we want to support array
+with runtime-defined rank, we can't deduce the correct shape, e.g. `[1]` vs.
+`[1, 1, 1]`, when read into an array.
+
+# Removal of `Object*Props`.
+To out knowledge these could not be used meaningfully. Please create an issue
+if you relied on these.
diff --git a/packages/HighFive/include/highfive/H5Attribute.hpp b/packages/HighFive/include/highfive/H5Attribute.hpp
index c34f9e49f..9fa3b63ba 100644
--- a/packages/HighFive/include/highfive/H5Attribute.hpp
+++ b/packages/HighFive/include/highfive/H5Attribute.hpp
@@ -13,6 +13,7 @@
 #include <H5Apublic.h>
 
 #include "H5DataType.hpp"
+#include "H5DataSpace.hpp"
 #include "H5Object.hpp"
 #include "bits/H5Friends.hpp"
 #include "bits/H5Path_traits.hpp"
@@ -70,7 +71,7 @@ class Attribute: public Object, public PathTraits<Attribute> {
     /// \since 1.0
     DataType getDataType() const;
 
-    /// \brief Get the DataSpace of the current Attribute.
+    /// \brief Get a copy of the DataSpace of the current Attribute.
     /// \code{.cpp}
     /// Attribute attr = dset.createAttribute<int>("foo", DataSpace(1, 2));
     /// auto dspace = attr.getSpace(); // This will be a DataSpace of dimension 1 * 2
@@ -78,8 +79,12 @@ class Attribute: public Object, public PathTraits<Attribute> {
     /// \since 1.0
     DataSpace getSpace() const;
 
-    /// \brief Get the DataSpace of the current Attribute.
-    /// \note This is an alias of getSpace().
+    /// \brief Get the memory DataSpace of the current Attribute.
+    ///
+    /// HDF5 attributes don't support selections. Therefore, there's no need
+    /// for a memory dataspace. However, HighFive supports allocating arrays
+    /// and checking dimensions, this requires the dimensions of the memspace.
+    ///
     /// \since 1.0
     DataSpace getMemSpace() const;
 
@@ -245,10 +250,34 @@ class Attribute: public Object, public PathTraits<Attribute> {
     // No empty attributes
     Attribute() = delete;
 
+    ///
+    /// \brief Return an `Attribute` with `axes` squeezed from the memspace.
+    ///
+    /// Returns an `Attribute` in which the memspace has been modified
+    /// to not include the axes listed in `axes`.
+    ///
+    /// Throws if any axis to be squeezes has a dimension other than `1`.
+    ///
+    /// \since 3.0
+    Attribute squeezeMemSpace(const std::vector<size_t>& axes) const;
+
+    ///
+    /// \brief Return a `Attribute` with a simple memspace with `dims`.
+    ///
+    /// Returns a `Attribute` in which the memspace has been modified
+    /// to be a simple dataspace with dimensions `dims`.
+    ///
+    /// Throws if the number of elements changes.
+    ///
+    /// \since 3.0
+    Attribute reshapeMemSpace(const std::vector<size_t>& dims) const;
+
   protected:
     using Object::Object;
 
   private:
+    DataSpace _mem_space;
+
 #if HIGHFIVE_HAS_FRIEND_DECLARATIONS
     template <typename Derivate>
     friend class ::HighFive::AnnotateTraits;
diff --git a/packages/HighFive/include/highfive/H5File.hpp b/packages/HighFive/include/highfive/H5File.hpp
index a8db5f2a1..b134aaa49 100644
--- a/packages/HighFive/include/highfive/H5File.hpp
+++ b/packages/HighFive/include/highfive/H5File.hpp
@@ -70,7 +70,7 @@ class File: public Object, public NodeTraits<File>, public AnnotateTraits<File>
     ///
     /// \brief Return the name of the file
     ///
-    const std::string& getName() const noexcept;
+    const std::string& getName() const;
 
 
     /// \brief Object path of a File is always "/"
diff --git a/packages/HighFive/include/highfive/H5Object.hpp b/packages/HighFive/include/highfive/H5Object.hpp
index 4cf4e7de0..b6058bf78 100644
--- a/packages/HighFive/include/highfive/H5Object.hpp
+++ b/packages/HighFive/include/highfive/H5Object.hpp
@@ -31,34 +31,12 @@ enum class ObjectType {
     Other  // Internal/custom object type
 };
 
-namespace detail {
-/// \brief Internal hack to create an `Object` from an ID.
-///
-/// WARNING: Creating an Object from an ID has implications w.r.t. the lifetime of the object
-///          that got passed via its ID. Using this method careless opens up the suite of issues
-///          related to C-style resource management, including the analog of double free, dangling
-///          pointers, etc.
-///
-/// NOTE: This is not part of the API and only serves to work around a compiler issue in GCC which
-///       prevents us from using `friend`s instead. This function should only be used for internal
-///       purposes. The problematic construct is:
-///
-///           template<class Derived>
-///           friend class SomeCRTP<Derived>;
-///
-/// \private
-Object make_object(hid_t hid);
-}  // namespace detail
-
 
 class Object {
   public:
     // move constructor, reuse hid
     Object(Object&& other) noexcept;
 
-    // decrease reference counter
-    ~Object();
-
     ///
     /// \brief isValid
     /// \return true if current Object is a valid HDF5Object
@@ -99,13 +77,15 @@ class Object {
     // Init with an low-level object id
     explicit Object(hid_t);
 
+    // decrease reference counter
+    ~Object();
+
     // Copy-Assignment operator
     Object& operator=(const Object& other);
 
     hid_t _hid;
 
   private:
-    friend Object detail::make_object(hid_t);
     friend class Reference;
     friend class CompoundType;
 
diff --git a/packages/HighFive/include/highfive/H5PropertyList.hpp b/packages/HighFive/include/highfive/H5PropertyList.hpp
index 2368f5ca9..5d467dbb3 100644
--- a/packages/HighFive/include/highfive/H5PropertyList.hpp
+++ b/packages/HighFive/include/highfive/H5PropertyList.hpp
@@ -87,7 +87,6 @@ namespace HighFive {
 /// \brief Types of property lists
 ///
 enum class PropertyType : int {
-    OBJECT_CREATE,
     FILE_CREATE,
     FILE_ACCESS,
     DATASET_CREATE,
@@ -99,7 +98,6 @@ enum class PropertyType : int {
     DATATYPE_ACCESS,
     STRING_CREATE,
     ATTRIBUTE_CREATE,
-    OBJECT_COPY,
     LINK_CREATE,
     LINK_ACCESS,
 };
@@ -195,7 +193,6 @@ class PropertyList: public PropertyListBase {
     void _initializeIfNeeded();
 };
 
-using ObjectCreateProps = PropertyList<PropertyType::OBJECT_CREATE>;
 using FileCreateProps = PropertyList<PropertyType::FILE_CREATE>;
 using FileAccessProps = PropertyList<PropertyType::FILE_ACCESS>;
 using DataSetCreateProps = PropertyList<PropertyType::DATASET_CREATE>;
@@ -207,7 +204,6 @@ using DataTypeCreateProps = PropertyList<PropertyType::DATATYPE_CREATE>;
 using DataTypeAccessProps = PropertyList<PropertyType::DATATYPE_ACCESS>;
 using StringCreateProps = PropertyList<PropertyType::STRING_CREATE>;
 using AttributeCreateProps = PropertyList<PropertyType::ATTRIBUTE_CREATE>;
-using ObjectCopyProps = PropertyList<PropertyType::OBJECT_COPY>;
 using LinkCreateProps = PropertyList<PropertyType::LINK_CREATE>;
 using LinkAccessProps = PropertyList<PropertyType::LINK_ACCESS>;
 
@@ -606,7 +602,6 @@ class CreateIntermediateGroup {
   public:
     explicit CreateIntermediateGroup(bool create = true);
 
-    explicit CreateIntermediateGroup(const ObjectCreateProps& ocpl);
     explicit CreateIntermediateGroup(const LinkCreateProps& lcpl);
 
     bool isSet() const;
@@ -615,7 +610,6 @@ class CreateIntermediateGroup {
     void fromPropertyList(hid_t hid);
 
   private:
-    friend ObjectCreateProps;
     friend LinkCreateProps;
     void apply(hid_t hid) const;
     bool _create;
diff --git a/packages/HighFive/include/highfive/H5Selection.hpp b/packages/HighFive/include/highfive/H5Selection.hpp
index c00c66d52..27681fe7a 100644
--- a/packages/HighFive/include/highfive/H5Selection.hpp
+++ b/packages/HighFive/include/highfive/H5Selection.hpp
@@ -30,21 +30,21 @@ class Selection: public SliceTraits<Selection> {
     /// \brief getSpace
     /// \return Dataspace associated with this selection
     ///
-    DataSpace getSpace() const noexcept;
+    DataSpace getSpace() const;
 
     ///
     /// \brief getMemSpace
     /// \return Dataspace associated with the memory representation of this
     /// selection
     ///
-    DataSpace getMemSpace() const noexcept;
+    DataSpace getMemSpace() const;
 
     ///
     /// \brief getDataSet
     /// \return parent dataset of this selection
     ///
-    DataSet& getDataset() noexcept;
-    const DataSet& getDataset() const noexcept;
+    DataSet& getDataset();
+    const DataSet& getDataset() const;
 
     ///
     /// \brief return the datatype of the selection
diff --git a/packages/HighFive/include/highfive/bits/H5Attribute_misc.hpp b/packages/HighFive/include/highfive/bits/H5Attribute_misc.hpp
index 19eceb49f..7ad38699a 100644
--- a/packages/HighFive/include/highfive/bits/H5Attribute_misc.hpp
+++ b/packages/HighFive/include/highfive/bits/H5Attribute_misc.hpp
@@ -8,6 +8,7 @@
  */
 #pragma once
 
+#include <H5Ipublic.h>
 #include <algorithm>
 #include <functional>
 #include <numeric>
@@ -18,10 +19,13 @@
 
 #include "../H5DataSpace.hpp"
 #include "H5Converter_misc.hpp"
+#include "H5Inspector_misc.hpp"
 #include "H5ReadWrite_misc.hpp"
 #include "H5Utils.hpp"
 #include "h5a_wrapper.hpp"
 #include "h5d_wrapper.hpp"
+#include "squeeze.hpp"
+#include "assert_compatible_spaces.hpp"
 
 namespace HighFive {
 
@@ -51,7 +55,7 @@ inline DataSpace Attribute::getSpace() const {
 }
 
 inline DataSpace Attribute::getMemSpace() const {
-    return getSpace();
+    return _mem_space.getId() == H5I_INVALID_HID ? getSpace() : _mem_space;
 }
 
 template <typename T>
@@ -70,19 +74,17 @@ inline void Attribute::read(T& array) const {
         [this]() -> std::string { return this->getName(); },
         details::BufferInfo<T>::Operation::read);
 
-    if (!details::checkDimensions(mem_space, buffer_info.n_dimensions)) {
+    if (!details::checkDimensions(mem_space, buffer_info.getMinRank(), buffer_info.getMaxRank())) {
         std::ostringstream ss;
-        ss << "Impossible to read Attribute of dimensions " << mem_space.getNumberDimensions()
-           << " into arrays of dimensions " << buffer_info.n_dimensions;
+        ss << "Impossible to read attribute of dimensions " << mem_space.getNumberDimensions()
+           << " into arrays of dimensions: " << buffer_info.getMinRank() << "(min) to "
+           << buffer_info.getMaxRank() << "(max)";
         throw DataSpaceException(ss.str());
     }
     auto dims = mem_space.getDimensions();
 
     if (mem_space.getElementCount() == 0) {
-        auto effective_dims = details::squeezeDimensions(dims,
-                                                         details::inspector<T>::recursive_ndim);
-
-        details::inspector<T>::prepare(array, effective_dims);
+        details::inspector<T>::prepare(array, dims);
         return;
     }
 
@@ -137,10 +139,11 @@ inline void Attribute::write(const T& buffer) {
         [this]() -> std::string { return this->getName(); },
         details::BufferInfo<T>::Operation::write);
 
-    if (!details::checkDimensions(mem_space, buffer_info.n_dimensions)) {
+    if (!details::checkDimensions(mem_space, buffer_info.getMinRank(), buffer_info.getMaxRank())) {
         std::ostringstream ss;
-        ss << "Impossible to write buffer of dimensions " << buffer_info.n_dimensions
-           << " into dataset of dimensions " << mem_space.getNumberDimensions();
+        ss << "Impossible to write attribute of dimensions " << mem_space.getNumberDimensions()
+           << " into arrays of dimensions: " << buffer_info.getMinRank() << "(min) to "
+           << buffer_info.getMaxRank() << "(max)";
         throw DataSpaceException(ss.str());
     }
     auto w = details::data_converter::serialize<T>(buffer, dims, file_datatype);
@@ -160,4 +163,21 @@ inline void Attribute::write_raw(const T* buffer) {
     write_raw(buffer, mem_datatype);
 }
 
+inline Attribute Attribute::squeezeMemSpace(const std::vector<size_t>& axes) const {
+    auto mem_dims = this->getMemSpace().getDimensions();
+    auto squeezed_dims = detail::squeeze(mem_dims, axes);
+
+    auto attr = *this;
+    attr._mem_space = DataSpace(squeezed_dims);
+    return attr;
+}
+
+inline Attribute Attribute::reshapeMemSpace(const std::vector<size_t>& new_dims) const {
+    detail::assert_compatible_spaces(this->getMemSpace(), new_dims);
+
+    auto attr = *this;
+    attr._mem_space = DataSpace(new_dims);
+    return attr;
+}
+
 }  // namespace HighFive
diff --git a/packages/HighFive/include/highfive/bits/H5Converter_misc.hpp b/packages/HighFive/include/highfive/bits/H5Converter_misc.hpp
index d1ba132c4..5fcbafb5e 100644
--- a/packages/HighFive/include/highfive/bits/H5Converter_misc.hpp
+++ b/packages/HighFive/include/highfive/bits/H5Converter_misc.hpp
@@ -415,10 +415,8 @@ struct data_converter {
     static Reader<T> get_reader(const std::vector<size_t>& dims,
                                 T& val,
                                 const DataType& file_datatype) {
-        // TODO Use bufferinfo for recursive_ndim
-        auto effective_dims = details::squeezeDimensions(dims, inspector<T>::recursive_ndim);
-        inspector<T>::prepare(val, effective_dims);
-        return Reader<T>(effective_dims, val, file_datatype);
+        inspector<T>::prepare(val, dims);
+        return Reader<T>(dims, val, file_datatype);
     }
 };
 
diff --git a/packages/HighFive/include/highfive/bits/H5DataType_misc.hpp b/packages/HighFive/include/highfive/bits/H5DataType_misc.hpp
index 4321a4658..797702188 100644
--- a/packages/HighFive/include/highfive/bits/H5DataType_misc.hpp
+++ b/packages/HighFive/include/highfive/bits/H5DataType_misc.hpp
@@ -226,9 +226,9 @@ inline EnumType<details::Boolean> create_enum_boolean() {
 // Other cases not supported. Fail early with a user message
 template <typename T>
 AtomicType<T>::AtomicType() {
-    static_assert(details::inspector<T>::recursive_ndim == 0,
-                  "Atomic types cant be arrays, except for char[] (fixed-length strings)");
-    static_assert(details::inspector<T>::recursive_ndim > 0, "Type not supported");
+    static_assert(
+        true,
+        "Missing specialization of AtomicType<T>. Therefore, type T is not supported by HighFive.");
 }
 
 
diff --git a/packages/HighFive/include/highfive/bits/H5Dataspace_misc.hpp b/packages/HighFive/include/highfive/bits/H5Dataspace_misc.hpp
index ceae1e531..4382c14c1 100644
--- a/packages/HighFive/include/highfive/bits/H5Dataspace_misc.hpp
+++ b/packages/HighFive/include/highfive/bits/H5Dataspace_misc.hpp
@@ -131,9 +131,10 @@ inline DataSpace DataSpace::FromCharArrayStrings(const char (&)[N][Width]) {
 
 namespace details {
 
-/// dimension checks @internal
-inline bool checkDimensions(const DataSpace& mem_space, size_t n_dim_requested) {
-    return checkDimensions(mem_space.getDimensions(), n_dim_requested);
+inline bool checkDimensions(const DataSpace& mem_space,
+                            size_t min_dim_requested,
+                            size_t max_dim_requested) {
+    return checkDimensions(mem_space.getDimensions(), min_dim_requested, max_dim_requested);
 }
 
 }  // namespace details
diff --git a/packages/HighFive/include/highfive/bits/H5File_misc.hpp b/packages/HighFive/include/highfive/bits/H5File_misc.hpp
index 52ae59516..6013953b1 100644
--- a/packages/HighFive/include/highfive/bits/H5File_misc.hpp
+++ b/packages/HighFive/include/highfive/bits/H5File_misc.hpp
@@ -82,7 +82,7 @@ inline File::File(const std::string& filename,
     _hid = detail::h5f_create(filename.c_str(), createMode, fcpl, fapl);
 }
 
-inline const std::string& File::getName() const noexcept {
+inline const std::string& File::getName() const {
     if (_filename.empty()) {
         _filename = details::get_name([this](char* buffer, size_t length) {
             return detail::h5f_get_name(getId(), buffer, length);
diff --git a/packages/HighFive/include/highfive/bits/H5Inspector_decl.hpp b/packages/HighFive/include/highfive/bits/H5Inspector_decl.hpp
index 434545a60..24b547e21 100644
--- a/packages/HighFive/include/highfive/bits/H5Inspector_decl.hpp
+++ b/packages/HighFive/include/highfive/bits/H5Inspector_decl.hpp
@@ -1,20 +1,12 @@
 #pragma once
 
-#include <cstddef>
-#include <numeric>
-#include <functional>
-#include <vector>
+#include "compute_total_size.hpp"
 
 namespace HighFive {
 
-inline size_t compute_total_size(const std::vector<size_t>& dims) {
-    return std::accumulate(dims.begin(), dims.end(), size_t{1u}, std::multiplies<size_t>());
-}
-
 template <typename T>
 using unqualified_t = typename std::remove_const<typename std::remove_reference<T>::type>::type;
 
-
 namespace details {
 
 template <typename T>
diff --git a/packages/HighFive/include/highfive/bits/H5Inspector_misc.hpp b/packages/HighFive/include/highfive/bits/H5Inspector_misc.hpp
index 3f69276c4..59bf85422 100644
--- a/packages/HighFive/include/highfive/bits/H5Inspector_misc.hpp
+++ b/packages/HighFive/include/highfive/bits/H5Inspector_misc.hpp
@@ -27,93 +27,20 @@
 namespace HighFive {
 namespace details {
 
-inline bool checkDimensions(const std::vector<size_t>& dims, size_t n_dim_requested) {
-    size_t n_dim_actual = dims.size();
-
-    // We should allow reading scalar from shapes like `(1, 1, 1)`.
-    if (n_dim_requested == 0) {
-        if (n_dim_actual == 0ul) {
-            return true;
-        }
-
-        return size_t(std::count(dims.begin(), dims.end(), 1ul)) == n_dim_actual;
-    }
-
-    // For non-scalar datasets, we can squeeze away singleton dimension, but
-    // we never add any.
-    if (n_dim_actual < n_dim_requested) {
-        return false;
-    }
-
-    // Special case for 1-dimensional arrays, which can squeeze `1`s from either
-    // side simultaneously if needed.
-    if (n_dim_requested == 1ul) {
-        return n_dim_actual >= 1ul &&
-               size_t(std::count(dims.begin(), dims.end(), 1ul)) >= n_dim_actual - 1ul;
+inline bool checkDimensions(const std::vector<size_t>& dims,
+                            size_t min_dim_requested,
+                            size_t max_dim_requested) {
+    if (min_dim_requested <= dims.size() && dims.size() <= max_dim_requested) {
+        return true;
     }
 
-    // All other cases strip front only. This avoid unstable behaviour when
-    // squeezing singleton dimensions.
-    size_t n_dim_excess = n_dim_actual - n_dim_requested;
 
-    bool squeeze_back = true;
-    for (size_t i = 1; i <= n_dim_excess; ++i) {
-        if (dims[n_dim_actual - i] != 1) {
-            squeeze_back = false;
-            break;
-        }
-    }
-
-    return squeeze_back;
+    // Scalar values still support broadcasting
+    // into arrays with one element.
+    size_t n_elements = compute_total_size(dims);
+    return n_elements == 1 && min_dim_requested == 0;
 }
 
-
-inline std::vector<size_t> squeezeDimensions(const std::vector<size_t>& dims,
-                                             size_t n_dim_requested) {
-    auto format_error_message = [&]() -> std::string {
-        return "Can't interpret dims = " + format_vector(dims) + " as " +
-               std::to_string(n_dim_requested) + "-dimensional.";
-    };
-
-    if (n_dim_requested == 0) {
-        if (!checkDimensions(dims, n_dim_requested)) {
-            throw std::invalid_argument("Failed dimensions check: " + format_error_message());
-        }
-
-        return {1ul};
-    }
-
-    auto n_dim = dims.size();
-    if (n_dim < n_dim_requested) {
-        throw std::invalid_argument("Failed 'n_dim < n_dim_requested: " + format_error_message());
-    }
-
-    if (n_dim_requested == 1ul) {
-        size_t non_singleton_dim = size_t(-1);
-        for (size_t i = 0; i < n_dim; ++i) {
-            if (dims[i] != 1ul) {
-                if (non_singleton_dim == size_t(-1)) {
-                    non_singleton_dim = i;
-                } else {
-                    throw std::invalid_argument("Failed one-dimensional: " +
-                                                format_error_message());
-                }
-            }
-        }
-
-        return {dims[std::min(non_singleton_dim, n_dim - 1)]};
-    }
-
-    size_t n_dim_excess = dims.size() - n_dim_requested;
-    for (size_t i = 1; i <= n_dim_excess; ++i) {
-        if (dims[n_dim - i] != 1) {
-            throw std::invalid_argument("Failed stripping from back:" + format_error_message());
-        }
-    }
-
-    return std::vector<size_t>(dims.begin(),
-                               dims.end() - static_cast<std::ptrdiff_t>(n_dim_excess));
-}
 }  // namespace details
 
 
@@ -125,13 +52,15 @@ inspector<T> {
     // hdf5_type is the base read by hdf5 (c-type) (e.g. std::vector<std::string> => const char*)
     using hdf5_type
 
-    // Number of dimensions starting from here
-    static constexpr size_t recursive_ndim
     // Is the inner type trivially copyable for optimisation
     // If this value is true: data() is mandatory
     // If this value is false: serialize, unserialize are mandatory
     static constexpr bool is_trivially_copyable
 
+    // Is this type trivially nestable, i.e. is type[n] a contiguous
+    // array of `base_type[N]`?
+    static constexpr bool is_trivially_nestable
+
     // Reading:
     // Allocate the value following dims (should be recursive)
     static void prepare(type& val, const std::vector<std::size_t> dims)
@@ -160,8 +89,15 @@ struct type_helper {
     using hdf5_type = base_type;
 
     static constexpr size_t ndim = 0;
-    static constexpr size_t recursive_ndim = ndim;
+    static constexpr size_t min_ndim = ndim;
+    static constexpr size_t max_ndim = ndim;
+
     static constexpr bool is_trivially_copyable = std::is_trivially_copyable<type>::value;
+    static constexpr bool is_trivially_nestable = is_trivially_copyable;
+
+    static size_t getRank(const type& /* val */) {
+        return ndim;
+    }
 
     static std::vector<size_t> getDimensions(const type& /* val */) {
         return {};
@@ -206,6 +142,7 @@ struct inspector<bool>: type_helper<bool> {
     using hdf5_type = int8_t;
 
     static constexpr bool is_trivially_copyable = false;
+    static constexpr bool is_trivially_nestable = false;
 
     static hdf5_type* data(type& /* val */) {
         throw DataSpaceException("A boolean cannot be read directly.");
@@ -255,6 +192,7 @@ struct inspector<Reference>: type_helper<Reference> {
     using hdf5_type = hobj_ref_t;
 
     static constexpr bool is_trivially_copyable = false;
+    static constexpr bool is_trivially_nestable = false;
 
     static hdf5_type* data(type& /* val */) {
         throw DataSpaceException("A Reference cannot be read directly.");
@@ -285,16 +223,27 @@ struct inspector<std::vector<T>> {
     using hdf5_type = typename inspector<value_type>::hdf5_type;
 
     static constexpr size_t ndim = 1;
-    static constexpr size_t recursive_ndim = ndim + inspector<value_type>::recursive_ndim;
+    static constexpr size_t min_ndim = ndim + inspector<value_type>::min_ndim;
+    static constexpr size_t max_ndim = ndim + inspector<value_type>::max_ndim;
+
     static constexpr bool is_trivially_copyable = std::is_trivially_copyable<value_type>::value &&
-                                                  inspector<value_type>::is_trivially_copyable;
+                                                  inspector<value_type>::is_trivially_nestable;
+    static constexpr bool is_trivially_nestable = false;
+
+    static size_t getRank(const type& val) {
+        if (!val.empty()) {
+            return ndim + inspector<value_type>::getRank(val[0]);
+        } else {
+            return min_ndim;
+        }
+    }
 
     static std::vector<size_t> getDimensions(const type& val) {
-        std::vector<size_t> sizes(recursive_ndim, 1ul);
+        auto rank = getRank(val);
+        std::vector<size_t> sizes(rank, 1ul);
         sizes[0] = val.size();
         if (!val.empty()) {
             auto s = inspector<value_type>::getDimensions(val[0]);
-            assert(s.size() + ndim == sizes.size());
             for (size_t i = 0; i < s.size(); ++i) {
                 sizes[i + ndim] = s[i];
             }
@@ -348,8 +297,15 @@ struct inspector<std::vector<bool>> {
     using hdf5_type = uint8_t;
 
     static constexpr size_t ndim = 1;
-    static constexpr size_t recursive_ndim = ndim;
+    static constexpr size_t min_ndim = ndim;
+    static constexpr size_t max_ndim = ndim;
+
     static constexpr bool is_trivially_copyable = false;
+    static constexpr bool is_trivially_nestable = false;
+
+    static size_t getRank(const type& /* val */) {
+        return ndim;
+    }
 
     static std::vector<size_t> getDimensions(const type& val) {
         std::vector<size_t> sizes{val.size()};
@@ -394,17 +350,22 @@ struct inspector<std::array<T, N>> {
     using hdf5_type = typename inspector<value_type>::hdf5_type;
 
     static constexpr size_t ndim = 1;
-    static constexpr size_t recursive_ndim = ndim + inspector<value_type>::recursive_ndim;
+    static constexpr size_t min_ndim = ndim + inspector<value_type>::min_ndim;
+    static constexpr size_t max_ndim = ndim + inspector<value_type>::max_ndim;
+
     static constexpr bool is_trivially_copyable = std::is_trivially_copyable<value_type>::value &&
-                                                  sizeof(type) == N * sizeof(T) &&
-                                                  inspector<value_type>::is_trivially_copyable;
+                                                  inspector<value_type>::is_trivially_nestable;
+    static constexpr bool is_trivially_nestable = (sizeof(type) == N * sizeof(T)) &&
+                                                  is_trivially_copyable;
+
+    static size_t getRank(const type& val) {
+        return ndim + inspector<value_type>::getRank(val[0]);
+    }
 
     static std::vector<size_t> getDimensions(const type& val) {
         std::vector<size_t> sizes{N};
-        if (!val.empty()) {
-            auto s = inspector<value_type>::getDimensions(val[0]);
-            sizes.insert(sizes.end(), s.begin(), s.end());
-        }
+        auto s = inspector<value_type>::getDimensions(val[0]);
+        sizes.insert(sizes.end(), s.begin(), s.end());
         return sizes;
     }
 
@@ -455,6 +416,7 @@ struct inspector<std::array<T, N>> {
     }
 };
 
+
 // Cannot be use for reading
 template <typename T>
 struct inspector<T*> {
@@ -464,9 +426,20 @@ struct inspector<T*> {
     using hdf5_type = typename inspector<value_type>::hdf5_type;
 
     static constexpr size_t ndim = 1;
-    static constexpr size_t recursive_ndim = ndim + inspector<value_type>::recursive_ndim;
+    static constexpr size_t min_ndim = ndim + inspector<value_type>::min_ndim;
+    static constexpr size_t max_ndim = ndim + inspector<value_type>::max_ndim;
+
     static constexpr bool is_trivially_copyable = std::is_trivially_copyable<value_type>::value &&
-                                                  inspector<value_type>::is_trivially_copyable;
+                                                  inspector<value_type>::is_trivially_nestable;
+    static constexpr bool is_trivially_nestable = false;
+
+    static size_t getRank(const type& val) {
+        if (val != nullptr) {
+            return ndim + inspector<value_type>::getRank(val[0]);
+        } else {
+            return min_ndim;
+        }
+    }
 
     static std::vector<size_t> getDimensions(const type& /* val */) {
         throw DataSpaceException("Not possible to have size of a T*");
@@ -494,9 +467,12 @@ struct inspector<T[N]> {
     using hdf5_type = typename inspector<value_type>::hdf5_type;
 
     static constexpr size_t ndim = 1;
-    static constexpr size_t recursive_ndim = ndim + inspector<value_type>::recursive_ndim;
+    static constexpr size_t min_ndim = ndim + inspector<value_type>::min_ndim;
+    static constexpr size_t max_ndim = ndim + inspector<value_type>::max_ndim;
+
     static constexpr bool is_trivially_copyable = std::is_trivially_copyable<value_type>::value &&
-                                                  inspector<value_type>::is_trivially_copyable;
+                                                  inspector<value_type>::is_trivially_nestable;
+    static constexpr bool is_trivially_nestable = is_trivially_copyable;
 
     static void prepare(type& val, const std::vector<size_t>& dims) {
         if (dims.size() < 1) {
@@ -513,12 +489,14 @@ struct inspector<T[N]> {
         }
     }
 
+    static size_t getRank(const type& val) {
+        return ndim + inspector<value_type>::getRank(val[0]);
+    }
+
     static std::vector<size_t> getDimensions(const type& val) {
         std::vector<size_t> sizes{N};
-        if (N > 0) {
-            auto s = inspector<value_type>::getDimensions(val[0]);
-            sizes.insert(sizes.end(), s.begin(), s.end());
-        }
+        auto s = inspector<value_type>::getDimensions(val[0]);
+        sizes.insert(sizes.end(), s.begin(), s.end());
         return sizes;
     }
 
@@ -541,5 +519,6 @@ struct inspector<T[N]> {
     }
 };
 
+
 }  // namespace details
 }  // namespace HighFive
diff --git a/packages/HighFive/include/highfive/bits/H5Node_traits.hpp b/packages/HighFive/include/highfive/bits/H5Node_traits.hpp
index 56d9f8d3a..8076bd9b4 100644
--- a/packages/HighFive/include/highfive/bits/H5Node_traits.hpp
+++ b/packages/HighFive/include/highfive/bits/H5Node_traits.hpp
@@ -217,9 +217,6 @@ class NodeTraits {
     // It makes behavior consistent among versions and by default transforms
     // errors to exceptions
     bool _exist(const std::string& node_name, bool raise_errors = true) const;
-
-    // Opens an arbitrary object to obtain info
-    Object _open(const std::string& node_name) const;
 };
 
 
diff --git a/packages/HighFive/include/highfive/bits/H5Node_traits_misc.hpp b/packages/HighFive/include/highfive/bits/H5Node_traits_misc.hpp
index 49cfc639d..b26257779 100644
--- a/packages/HighFive/include/highfive/bits/H5Node_traits_misc.hpp
+++ b/packages/HighFive/include/highfive/bits/H5Node_traits_misc.hpp
@@ -254,7 +254,12 @@ inline LinkType NodeTraits<Derivate>::getLinkType(const std::string& node_name)
 
 template <typename Derivate>
 inline ObjectType NodeTraits<Derivate>::getObjectType(const std::string& node_name) const {
-    return _open(node_name).getType();
+    const auto id = detail::h5o_open(static_cast<const Derivate*>(this)->getId(),
+                                     node_name.c_str(),
+                                     H5P_DEFAULT);
+    auto object_type = _convert_object_type(detail::h5i_get_type(id));
+    detail::h5o_close(id);
+    return object_type;
 }
 
 
@@ -314,13 +319,4 @@ inline void NodeTraits<Derivate>::createHardLink(const std::string& link_name,
 }
 
 
-template <typename Derivate>
-inline Object NodeTraits<Derivate>::_open(const std::string& node_name) const {
-    const auto id = detail::h5o_open(static_cast<const Derivate*>(this)->getId(),
-                                     node_name.c_str(),
-                                     H5P_DEFAULT);
-    return detail::make_object(id);
-}
-
-
 }  // namespace HighFive
diff --git a/packages/HighFive/include/highfive/bits/H5Object_misc.hpp b/packages/HighFive/include/highfive/bits/H5Object_misc.hpp
index c5a1f3999..eefddc1ed 100644
--- a/packages/HighFive/include/highfive/bits/H5Object_misc.hpp
+++ b/packages/HighFive/include/highfive/bits/H5Object_misc.hpp
@@ -15,12 +15,6 @@
 #include "h5i_wrapper.hpp"
 
 namespace HighFive {
-namespace detail {
-inline Object make_object(hid_t hid) {
-    return Object(hid);
-}
-}  // namespace detail
-
 
 inline Object::Object()
     : _hid(H5I_INVALID_HID) {}
diff --git a/packages/HighFive/include/highfive/bits/H5PropertyList_misc.hpp b/packages/HighFive/include/highfive/bits/H5PropertyList_misc.hpp
index 1fa2101f2..cfeb7685d 100644
--- a/packages/HighFive/include/highfive/bits/H5PropertyList_misc.hpp
+++ b/packages/HighFive/include/highfive/bits/H5PropertyList_misc.hpp
@@ -17,8 +17,6 @@ inline hid_t convert_plist_type(PropertyType propertyType) {
     // The HP5_XXX are macros with function calls so we can't assign
     // them as the enum values
     switch (propertyType) {
-    case PropertyType::OBJECT_CREATE:
-        return H5P_OBJECT_CREATE;
     case PropertyType::FILE_CREATE:
         return H5P_FILE_CREATE;
     case PropertyType::FILE_ACCESS:
@@ -41,8 +39,6 @@ inline hid_t convert_plist_type(PropertyType propertyType) {
         return H5P_STRING_CREATE;
     case PropertyType::ATTRIBUTE_CREATE:
         return H5P_ATTRIBUTE_CREATE;
-    case PropertyType::OBJECT_COPY:
-        return H5P_OBJECT_COPY;
     case PropertyType::LINK_CREATE:
         return H5P_LINK_CREATE;
     case PropertyType::LINK_ACCESS:
@@ -390,11 +386,6 @@ inline double Caching::getW0() const {
 inline CreateIntermediateGroup::CreateIntermediateGroup(bool create)
     : _create(create) {}
 
-inline CreateIntermediateGroup::CreateIntermediateGroup(const ObjectCreateProps& ocpl) {
-    fromPropertyList(ocpl.getId());
-}
-
-
 inline void CreateIntermediateGroup::apply(const hid_t hid) const {
     detail::h5p_set_create_intermediate_group(hid, _create ? 1 : 0);
 }
diff --git a/packages/HighFive/include/highfive/bits/H5ReadWrite_misc.hpp b/packages/HighFive/include/highfive/bits/H5ReadWrite_misc.hpp
index 05bb49888..e5c862bc5 100644
--- a/packages/HighFive/include/highfive/bits/H5ReadWrite_misc.hpp
+++ b/packages/HighFive/include/highfive/bits/H5ReadWrite_misc.hpp
@@ -9,6 +9,7 @@
 #pragma once
 
 #include <H5Tpublic.h>
+#include "H5Inspector_misc.hpp"
 #include "H5Utils.hpp"
 
 namespace HighFive {
@@ -57,10 +58,14 @@ struct BufferInfo {
     template <class F>
     BufferInfo(const DataType& dtype, F getName, Operation _op);
 
+    size_t getRank(const T& array) const;
+    size_t getMinRank() const;
+    size_t getMaxRank() const;
+
     // member data for info depending on the destination dataset type
     const bool is_fixed_len_string;
-    const size_t n_dimensions;
     const DataType data_type;
+    const size_t rank_correction;
 };
 
 // details implementation
@@ -135,10 +140,9 @@ BufferInfo<T>::BufferInfo(const DataType& file_data_type, F getName, Operation _
     : op(_op)
     , is_fixed_len_string(file_data_type.isFixedLenStr())
     // In case we are using Fixed-len strings we need to subtract one dimension
-    , n_dimensions(details::inspector<type_no_const>::recursive_ndim -
-                   ((is_fixed_len_string && is_char_array) ? 1 : 0))
     , data_type(string_type_checker<char_array_t>::getDataType(create_datatype<elem_type>(),
-                                                               file_data_type)) {
+                                                               file_data_type))
+    , rank_correction((is_fixed_len_string && is_char_array) ? 1 : 0) {
     // We warn. In case they are really not convertible an exception will rise on read/write
     if (file_data_type.getClass() != data_type.getClass()) {
         HIGHFIVE_LOG_WARN(getName() + "\": data and hdf5 dataset have different types: " +
@@ -157,6 +161,21 @@ BufferInfo<T>::BufferInfo(const DataType& file_data_type, F getName, Operation _
     }
 }
 
+template <typename T>
+size_t BufferInfo<T>::getRank(const T& array) const {
+    return details::inspector<type_no_const>::getRank(array) - rank_correction;
+}
+
+template <typename T>
+size_t BufferInfo<T>::getMinRank() const {
+    return details::inspector<T>::min_ndim - rank_correction;
+}
+
+template <typename T>
+size_t BufferInfo<T>::getMaxRank() const {
+    return details::inspector<T>::max_ndim - rank_correction;
+}
+
 }  // namespace details
 
 }  // namespace HighFive
diff --git a/packages/HighFive/include/highfive/bits/H5Selection_misc.hpp b/packages/HighFive/include/highfive/bits/H5Selection_misc.hpp
index c35b7bbf3..d1c14e930 100644
--- a/packages/HighFive/include/highfive/bits/H5Selection_misc.hpp
+++ b/packages/HighFive/include/highfive/bits/H5Selection_misc.hpp
@@ -17,19 +17,19 @@ inline Selection::Selection(const DataSpace& memspace,
     , _file_space(file_space)
     , _set(set) {}
 
-inline DataSpace Selection::getSpace() const noexcept {
+inline DataSpace Selection::getSpace() const {
     return _file_space;
 }
 
-inline DataSpace Selection::getMemSpace() const noexcept {
+inline DataSpace Selection::getMemSpace() const {
     return _mem_space;
 }
 
-inline DataSet& Selection::getDataset() noexcept {
+inline DataSet& Selection::getDataset() {
     return _set;
 }
 
-inline const DataSet& Selection::getDataset() const noexcept {
+inline const DataSet& Selection::getDataset() const {
     return _set;
 }
 
diff --git a/packages/HighFive/include/highfive/bits/H5Slice_traits.hpp b/packages/HighFive/include/highfive/bits/H5Slice_traits.hpp
index fd8c31d27..e2b481a36 100644
--- a/packages/HighFive/include/highfive/bits/H5Slice_traits.hpp
+++ b/packages/HighFive/include/highfive/bits/H5Slice_traits.hpp
@@ -13,6 +13,7 @@
 
 #include "H5_definitions.hpp"
 #include "H5Utils.hpp"
+#include "convert_size_vector.hpp"
 
 #include "../H5PropertyList.hpp"
 #include "h5s_wrapper.hpp"
@@ -51,17 +52,6 @@ class ElementSet {
     friend class SliceTraits;
 };
 
-namespace detail {
-
-template <class To, class From>
-inline std::vector<To> convertSizeVector(const std::vector<From>& from) {
-    std::vector<To> to(from.size());
-    std::copy(from.cbegin(), from.cend(), to.begin());
-
-    return to;
-}
-}  // namespace detail
-
 inline std::vector<hsize_t> toHDF5SizeVector(const std::vector<size_t>& from) {
     return detail::convertSizeVector<hsize_t>(from);
 }
@@ -73,10 +63,10 @@ inline std::vector<size_t> toSTLSizeVector(const std::vector<hsize_t>& from) {
 struct RegularHyperSlab {
     RegularHyperSlab() = default;
 
-    RegularHyperSlab(std::vector<size_t> offset_,
-                     std::vector<size_t> count_ = {},
-                     std::vector<size_t> stride_ = {},
-                     std::vector<size_t> block_ = {})
+    RegularHyperSlab(const std::vector<size_t>& offset_,
+                     const std::vector<size_t>& count_ = {},
+                     const std::vector<size_t>& stride_ = {},
+                     const std::vector<size_t>& block_ = {})
         : offset(toHDF5SizeVector(offset_))
         , count(toHDF5SizeVector(count_))
         , stride(toHDF5SizeVector(stride_))
@@ -87,10 +77,10 @@ struct RegularHyperSlab {
                                           std::vector<hsize_t> stride_ = {},
                                           std::vector<hsize_t> block_ = {}) {
         RegularHyperSlab slab;
-        slab.offset = offset_;
-        slab.count = count_;
-        slab.stride = stride_;
-        slab.block = block_;
+        slab.offset = std::move(offset_);
+        slab.count = std::move(count_);
+        slab.stride = std::move(stride_);
+        slab.block = std::move(block_);
 
         return slab;
     }
@@ -368,6 +358,28 @@ class SliceTraits {
     ///
     template <typename T>
     void write_raw(const T* buffer, const DataTransferProps& xfer_props = DataTransferProps());
+
+    ///
+    /// \brief Return a `Selection` with `axes` squeezed from the memspace.
+    ///
+    /// Returns a selection in which the memspace has been modified
+    /// to not include the axes listed in `axes`.
+    ///
+    /// Throws if any axis to be squeezes has a dimension other than `1`.
+    ///
+    /// \since 3.0
+    Selection squeezeMemSpace(const std::vector<size_t>& axes) const;
+
+    ///
+    /// \brief Return a `Selection` with a simple memspace with `dims`.
+    ///
+    /// Returns a selection in which the memspace has been modified
+    /// to be a simple dataspace with dimensions `dims`.
+    ///
+    /// Throws if the number of elements changes.
+    ///
+    /// \since 3.0
+    Selection reshapeMemSpace(const std::vector<size_t>& dims) const;
 };
 
 }  // namespace HighFive
diff --git a/packages/HighFive/include/highfive/bits/H5Slice_traits_misc.hpp b/packages/HighFive/include/highfive/bits/H5Slice_traits_misc.hpp
index 2ae6640b0..711d57ac2 100644
--- a/packages/HighFive/include/highfive/bits/H5Slice_traits_misc.hpp
+++ b/packages/HighFive/include/highfive/bits/H5Slice_traits_misc.hpp
@@ -20,6 +20,9 @@
 
 #include "H5ReadWrite_misc.hpp"
 #include "H5Converter_misc.hpp"
+#include "squeeze.hpp"
+#include "compute_total_size.hpp"
+#include "assert_compatible_spaces.hpp"
 
 namespace HighFive {
 
@@ -177,10 +180,11 @@ inline void SliceTraits<Derivate>::read(T& array, const DataTransferProps& xfer_
         [&slice]() -> std::string { return details::get_dataset(slice).getPath(); },
         details::BufferInfo<T>::Operation::read);
 
-    if (!details::checkDimensions(mem_space, buffer_info.n_dimensions)) {
+    if (!details::checkDimensions(mem_space, buffer_info.getMinRank(), buffer_info.getMaxRank())) {
         std::ostringstream ss;
         ss << "Impossible to read DataSet of dimensions " << mem_space.getNumberDimensions()
-           << " into arrays of dimensions " << buffer_info.n_dimensions;
+           << " into arrays of dimensions: " << buffer_info.getMinRank() << "(min) to "
+           << buffer_info.getMaxRank() << "(max)";
         throw DataSpaceException(ss.str());
     }
     auto dims = mem_space.getDimensions();
@@ -251,11 +255,11 @@ inline void SliceTraits<Derivate>::write(const T& buffer, const DataTransferProp
         [&slice]() -> std::string { return details::get_dataset(slice).getPath(); },
         details::BufferInfo<T>::Operation::write);
 
-    if (!details::checkDimensions(mem_space, buffer_info.n_dimensions)) {
+    if (!details::checkDimensions(mem_space, buffer_info.getMinRank(), buffer_info.getMaxRank())) {
         std::ostringstream ss;
-        ss << "Impossible to write buffer of dimensions "
-           << details::format_vector(mem_space.getDimensions())
-           << " into dataset with n = " << buffer_info.n_dimensions << " dimensions.";
+        ss << "Impossible to write buffer with dimensions n = " << buffer_info.getRank(buffer)
+           << "into dataset with dimensions " << details::format_vector(mem_space.getDimensions())
+           << ".";
         throw DataSpaceException(ss.str());
     }
     auto w = details::data_converter::serialize<T>(buffer, dims, file_datatype);
@@ -288,5 +292,34 @@ inline void SliceTraits<Derivate>::write_raw(const T* buffer, const DataTransfer
     write_raw(buffer, mem_datatype, xfer_props);
 }
 
+namespace detail {
+inline const DataSet& getDataSet(const Selection& selection) {
+    return selection.getDataset();
+}
+
+inline const DataSet& getDataSet(const DataSet& dataset) {
+    return dataset;
+}
+
+}  // namespace detail
+
+template <typename Derivate>
+inline Selection SliceTraits<Derivate>::squeezeMemSpace(const std::vector<size_t>& axes) const {
+    auto slice = static_cast<const Derivate&>(*this);
+    auto mem_dims = slice.getMemSpace().getDimensions();
+    auto squeezed_dims = detail::squeeze(mem_dims, axes);
+
+    return detail::make_selection(DataSpace(squeezed_dims),
+                                  slice.getSpace(),
+                                  detail::getDataSet(slice));
+}
+
+template <typename Derivate>
+inline Selection SliceTraits<Derivate>::reshapeMemSpace(const std::vector<size_t>& new_dims) const {
+    auto slice = static_cast<const Derivate&>(*this);
+
+    detail::assert_compatible_spaces(slice.getMemSpace(), new_dims);
+    return detail::make_selection(DataSpace(new_dims), slice.getSpace(), detail::getDataSet(slice));
+}
 
 }  // namespace HighFive
diff --git a/packages/HighFive/include/highfive/bits/assert_compatible_spaces.hpp b/packages/HighFive/include/highfive/bits/assert_compatible_spaces.hpp
new file mode 100644
index 000000000..f4be279d4
--- /dev/null
+++ b/packages/HighFive/include/highfive/bits/assert_compatible_spaces.hpp
@@ -0,0 +1,29 @@
+/*
+ *  Copyright (c), 2024, BlueBrain Project, EPFL
+ *
+ *  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)
+ *
+ */
+#pragma once
+
+#include <vector>
+#include "../H5Exception.hpp"
+#include "../H5DataSpace.hpp"
+
+namespace HighFive {
+namespace detail {
+
+inline void assert_compatible_spaces(const DataSpace& old, const std::vector<size_t>& dims) {
+    auto n_elements_old = old.getElementCount();
+    auto n_elements_new = dims.size() == 0 ? 1 : compute_total_size(dims);
+
+    if (n_elements_old != n_elements_new) {
+        throw Exception("Invalid parameter `new_dims` number of elements differ: " +
+                        std::to_string(n_elements_old) + " (old) vs. " +
+                        std::to_string(n_elements_new) + " (new)");
+    }
+}
+}  // namespace detail
+}  // namespace HighFive
diff --git a/packages/HighFive/include/highfive/bits/compute_total_size.hpp b/packages/HighFive/include/highfive/bits/compute_total_size.hpp
new file mode 100644
index 000000000..5be8a5999
--- /dev/null
+++ b/packages/HighFive/include/highfive/bits/compute_total_size.hpp
@@ -0,0 +1,14 @@
+#pragma once
+
+#include <cstddef>
+#include <numeric>
+#include <functional>
+#include <vector>
+
+namespace HighFive {
+
+inline size_t compute_total_size(const std::vector<size_t>& dims) {
+    return std::accumulate(dims.begin(), dims.end(), size_t{1u}, std::multiplies<size_t>());
+}
+
+}  // namespace HighFive
diff --git a/packages/HighFive/include/highfive/bits/convert_size_vector.hpp b/packages/HighFive/include/highfive/bits/convert_size_vector.hpp
new file mode 100644
index 000000000..62a815ae5
--- /dev/null
+++ b/packages/HighFive/include/highfive/bits/convert_size_vector.hpp
@@ -0,0 +1,31 @@
+/*
+ *  Copyright (c), 2017, Adrien Devresse <adrien.devresse@epfl.ch>
+ *  Copyright (c), 2017-2024, BlueBrain Project, EPFL
+ *
+ *  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)
+ *
+ */
+#pragma once
+
+#include <vector>
+
+namespace HighFive {
+namespace detail {
+
+template <class To, class From, class It = From const*>
+inline std::vector<To> convertSizeVector(const It& begin, const It& end) {
+    std::vector<To> to(static_cast<size_t>(end - begin));
+    std::copy(begin, end, to.begin());
+
+    return to;
+}
+
+template <class To, class From>
+inline std::vector<To> convertSizeVector(const std::vector<From>& from) {
+    return convertSizeVector<To, From>(from.cbegin(), from.cend());
+}
+
+}  // namespace detail
+}  // namespace HighFive
diff --git a/packages/HighFive/include/highfive/bits/h5o_wrapper.hpp b/packages/HighFive/include/highfive/bits/h5o_wrapper.hpp
index 75b91bb6a..df97c3ca1 100644
--- a/packages/HighFive/include/highfive/bits/h5o_wrapper.hpp
+++ b/packages/HighFive/include/highfive/bits/h5o_wrapper.hpp
@@ -15,5 +15,14 @@ inline hid_t h5o_open(hid_t loc_id, const char* name, hid_t lapl_id) {
     return hid;
 }
 
+inline herr_t h5o_close(hid_t id) {
+    herr_t err = H5Oclose(id);
+    if (err < 0) {
+        HDF5ErrMapper::ToException<ObjectException>("Unable to close object.");
+    }
+
+    return err;
+}
+
 }  // namespace detail
 }  // namespace HighFive
diff --git a/packages/HighFive/include/highfive/bits/squeeze.hpp b/packages/HighFive/include/highfive/bits/squeeze.hpp
new file mode 100644
index 000000000..4be610e34
--- /dev/null
+++ b/packages/HighFive/include/highfive/bits/squeeze.hpp
@@ -0,0 +1,54 @@
+/*
+ *  Copyright (c), 2024, BlueBrain Project, EPFL
+ *
+ *  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)
+ *
+ */
+#pragma once
+
+#include <vector>
+#include "../H5Exception.hpp"
+
+namespace HighFive {
+namespace detail {
+
+/// \brief Squeeze `axes` from `dims`.
+///
+/// An axis can only be squeezed if it's dimension is `1`. The elements of
+/// `axes` must be in the range `0, ..., dims.size()` (exclusive) and don't
+/// have to be sorted.
+///
+/// Example:
+///   squeeze({1, 3, 2, 1}, {0, 3}) == {3, 2}
+inline std::vector<size_t> squeeze(const std::vector<size_t>& dims,
+                                   const std::vector<size_t>& axes) {
+    auto n_dims = dims.size();
+    auto mask = std::vector<bool>(n_dims, false);
+    for (size_t i = 0; i < axes.size(); ++i) {
+        if (axes[i] >= n_dims) {
+            throw Exception("Out of range: axes[" + std::to_string(i) +
+                            "] == " + std::to_string(axes[i]) + " >= " + std::to_string(n_dims));
+        }
+
+        mask[axes[i]] = true;
+    }
+
+    auto squeezed_dims = std::vector<size_t>{};
+    for (size_t i = 0; i < n_dims; ++i) {
+        if (!mask[i]) {
+            squeezed_dims.push_back(dims[i]);
+        } else {
+            if (dims[i] != 1) {
+                throw Exception("Squeezing non-unity axis: axes[" + std::to_string(i) +
+                                "] = " + std::to_string(axes[i]));
+            }
+        }
+    }
+
+    return squeezed_dims;
+}
+
+}  // namespace detail
+}  // namespace HighFive
diff --git a/packages/HighFive/include/highfive/boost.hpp b/packages/HighFive/include/highfive/boost.hpp
index fb8a709c5..33c1458df 100644
--- a/packages/HighFive/include/highfive/boost.hpp
+++ b/packages/HighFive/include/highfive/boost.hpp
@@ -17,17 +17,31 @@ struct inspector<boost::multi_array<T, Dims>> {
     using hdf5_type = typename inspector<value_type>::hdf5_type;
 
     static constexpr size_t ndim = Dims;
-    static constexpr size_t recursive_ndim = ndim + inspector<value_type>::recursive_ndim;
+    static constexpr size_t min_ndim = ndim + inspector<value_type>::min_ndim;
+    static constexpr size_t max_ndim = ndim + inspector<value_type>::max_ndim;
+
     static constexpr bool is_trivially_copyable = std::is_trivially_copyable<value_type>::value &&
-                                                  inspector<value_type>::is_trivially_copyable;
+                                                  inspector<value_type>::is_trivially_nestable;
+    static constexpr bool is_trivially_nestable = false;
+
+
+    static size_t getRank(const type& val) {
+        return ndim + inspector<value_type>::getRank(val.data()[0]);
+    }
 
     static std::vector<size_t> getDimensions(const type& val) {
-        std::vector<size_t> sizes;
+        auto rank = getRank(val);
+        std::vector<size_t> sizes(rank, 1ul);
         for (size_t i = 0; i < ndim; ++i) {
-            sizes.push_back(val.shape()[i]);
+            sizes[i] = val.shape()[i];
+        }
+        if (val.size() != 0) {
+            auto s = inspector<value_type>::getDimensions(val.data()[0]);
+            sizes.resize(ndim + s.size());
+            for (size_t i = 0; i < s.size(); ++i) {
+                sizes[ndim + i] = s[i];
+            }
         }
-        auto s = inspector<value_type>::getDimensions(val.data()[0]);
-        sizes.insert(sizes.end(), s.begin(), s.end());
         return sizes;
     }
 
@@ -51,16 +65,25 @@ struct inspector<boost::multi_array<T, Dims>> {
         }
     }
 
+    static void assert_c_order(const type& val) {
+        if (!(val.storage_order() == boost::c_storage_order())) {
+            throw DataTypeException("Only C storage order is supported for 'boost::multi_array'.");
+        }
+    }
+
     static hdf5_type* data(type& val) {
+        assert_c_order(val);
         return inspector<value_type>::data(*val.data());
     }
 
     static const hdf5_type* data(const type& val) {
+        assert_c_order(val);
         return inspector<value_type>::data(*val.data());
     }
 
     template <class It>
     static void serialize(const type& val, const std::vector<size_t>& dims, It m) {
+        assert_c_order(val);
         size_t size = val.num_elements();
         auto subdims = std::vector<size_t>(dims.begin() + ndim, dims.end());
         size_t subsize = compute_total_size(subdims);
@@ -71,6 +94,7 @@ struct inspector<boost::multi_array<T, Dims>> {
 
     template <class It>
     static void unserialize(It vec_align, const std::vector<size_t>& dims, type& val) {
+        assert_c_order(val);
         std::vector<size_t> next_dims(dims.begin() + ndim, dims.end());
         size_t subsize = compute_total_size(next_dims);
         for (size_t i = 0; i < val.num_elements(); ++i) {
@@ -89,9 +113,16 @@ struct inspector<boost::numeric::ublas::matrix<T>> {
     using hdf5_type = typename inspector<value_type>::hdf5_type;
 
     static constexpr size_t ndim = 2;
-    static constexpr size_t recursive_ndim = ndim + inspector<value_type>::recursive_ndim;
+    static constexpr size_t min_ndim = ndim + inspector<value_type>::min_ndim;
+    static constexpr size_t max_ndim = ndim + inspector<value_type>::max_ndim;
+
     static constexpr bool is_trivially_copyable = std::is_trivially_copyable<value_type>::value &&
                                                   inspector<value_type>::is_trivially_copyable;
+    static constexpr bool is_trivially_nestable = false;
+
+    static size_t getRank(const type& val) {
+        return ndim + inspector<value_type>::getRank(val(0, 0));
+    }
 
     static std::vector<size_t> getDimensions(const type& val) {
         std::vector<size_t> sizes{val.size1(), val.size2()};
diff --git a/packages/HighFive/include/highfive/eigen.hpp b/packages/HighFive/include/highfive/eigen.hpp
index aaad280ef..462769e4b 100644
--- a/packages/HighFive/include/highfive/eigen.hpp
+++ b/packages/HighFive/include/highfive/eigen.hpp
@@ -16,6 +16,7 @@ struct eigen_inspector {
     using base_type = typename inspector<value_type>::base_type;
     using hdf5_type = base_type;
 
+
     static_assert(int(EigenType::ColsAtCompileTime) == int(EigenType::MaxColsAtCompileTime),
                   "Padding isn't supported.");
     static_assert(int(EigenType::RowsAtCompileTime) == int(EigenType::MaxRowsAtCompileTime),
@@ -26,11 +27,18 @@ struct eigen_inspector {
                EigenType::IsRowMajor;
     }
 
+
     static constexpr size_t ndim = 2;
-    static constexpr size_t recursive_ndim = ndim + inspector<value_type>::recursive_ndim;
+    static constexpr size_t min_ndim = ndim + inspector<value_type>::min_ndim;
+    static constexpr size_t max_ndim = ndim + inspector<value_type>::max_ndim;
     static constexpr bool is_trivially_copyable = is_row_major() &&
                                                   std::is_trivially_copyable<value_type>::value &&
-                                                  inspector<value_type>::is_trivially_copyable;
+                                                  inspector<value_type>::is_trivially_nestable;
+    static constexpr bool is_trivially_nestable = false;
+
+    static size_t getRank(const type& val) {
+        return ndim + inspector<value_type>::getRank(val.data()[0]);
+    }
 
     static std::vector<size_t> getDimensions(const type& val) {
         std::vector<size_t> sizes{static_cast<size_t>(val.rows()), static_cast<size_t>(val.cols())};
diff --git a/packages/HighFive/include/highfive/experimental/opencv.hpp b/packages/HighFive/include/highfive/experimental/opencv.hpp
new file mode 100644
index 000000000..224160975
--- /dev/null
+++ b/packages/HighFive/include/highfive/experimental/opencv.hpp
@@ -0,0 +1,149 @@
+#pragma once
+
+#include "../bits/H5Inspector_decl.hpp"
+#include "../H5Exception.hpp"
+
+#include <opencv2/opencv.hpp>
+
+#include "../bits/convert_size_vector.hpp"
+
+namespace HighFive {
+namespace details {
+
+
+template <class T>
+struct inspector<cv::Mat_<T>> {
+    using type = cv::Mat_<T>;
+    using value_type = T;
+    using base_type = typename inspector<value_type>::base_type;
+    using hdf5_type = base_type;
+
+    static void assert_row_major(const type& type) {
+        // Documentation claims that Mat_ is always row-major. However, it
+        // could be padded. The steps/strides are in bytes.
+        int rank = type.dims;
+        size_t ld = sizeof(T);
+        for (int i = rank - 1; i >= 0; --i) {
+            if (static_cast<size_t>(type.step[i]) != ld) {
+                throw DataSetException("Padded cv::Mat_ are not supported.");
+            }
+
+            ld *= static_cast<size_t>(type.size[i]);
+        }
+    }
+
+
+    static constexpr size_t min_ndim = 2 + inspector<value_type>::min_ndim;
+    static constexpr size_t max_ndim = 1024 + inspector<value_type>::max_ndim;
+
+    // HighFive doesn't support padded OpenCV arrays. Therefore, pretend
+    // that they themselves are trivially copyable. And error out if the
+    // assumption is violated.
+    static constexpr bool is_trivially_copyable = std::is_trivially_copyable<value_type>::value &&
+                                                  inspector<value_type>::is_trivially_nestable;
+    static constexpr bool is_trivially_nestable = false;
+
+    static size_t getRank(const type& val) {
+        if (val.empty()) {
+            return min_ndim;
+
+        } else {
+            return static_cast<size_t>(val.dims) +
+                   inspector<value_type>::getRank(getAnyElement(val));
+        }
+    }
+
+    static const T& getAnyElement(const type& val) {
+        return *reinterpret_cast<T const*>(val.data);
+    }
+
+    static T& getAnyElement(type& val) {
+        return *reinterpret_cast<T*>(val.data);
+    }
+
+    static size_t getLocalRank(const type& val) {
+        return static_cast<size_t>(val.dims);
+    }
+
+    static std::vector<size_t> getDimensions(const type& val) {
+        auto local_rank = getLocalRank(val);
+        auto rank = getRank(val);
+        std::vector<size_t> dims(rank, 1ul);
+
+        if (val.empty()) {
+            dims[0] = 0ul;
+            dims[1] = 1ul;
+            return dims;
+        }
+
+        for (size_t i = 0; i < local_rank; ++i) {
+            dims[i] = static_cast<size_t>(val.size[static_cast<int>(i)]);
+        }
+
+        auto s = inspector<value_type>::getDimensions(getAnyElement(val));
+        std::copy(s.cbegin(), s.cend(), dims.begin() + static_cast<int>(local_rank));
+        return dims;
+    }
+
+    static void prepare(type& val, const std::vector<size_t>& dims) {
+        auto subdims = detail::convertSizeVector<int>(dims);
+        val.create(static_cast<int>(subdims.size()), subdims.data());
+    }
+
+    static hdf5_type* data(type& val) {
+        assert_row_major(val);
+
+        if (!is_trivially_copyable) {
+            throw DataSetException("Invalid used of `inspector<Eigen::Matrix<...>>::data`.");
+        }
+
+        if (val.empty()) {
+            return nullptr;
+        }
+
+        return inspector<value_type>::data(getAnyElement(val));
+    }
+
+    static const hdf5_type* data(const type& val) {
+        assert_row_major(val);
+
+        if (!is_trivially_copyable) {
+            throw DataSetException("Invalid used of `inspector<Eigen::Matrix<...>>::data`.");
+        }
+
+        if (val.empty()) {
+            return nullptr;
+        }
+
+        return inspector<value_type>::data(getAnyElement(val));
+    }
+
+    static void serialize(const type& val, const std::vector<size_t>& dims, hdf5_type* m) {
+        if (val.empty()) {
+            return;
+        }
+
+        auto local_rank = val.dims;
+        auto subdims = std::vector<size_t>(dims.begin() + local_rank, dims.end());
+        auto subsize = compute_total_size(subdims);
+        for (auto it = val.begin(); it != val.end(); ++it) {
+            inspector<value_type>::serialize(*it, subdims, m);
+            m += subsize;
+        }
+    }
+
+    static void unserialize(const hdf5_type* vec_align,
+                            const std::vector<size_t>& dims,
+                            type& val) {
+        auto local_rank = val.dims;
+        auto subdims = std::vector<size_t>(dims.begin() + local_rank, dims.end());
+        auto subsize = compute_total_size(subdims);
+        for (auto it = val.begin(); it != val.end(); ++it) {
+            inspector<value_type>::unserialize(vec_align, subdims, *it);
+            vec_align += subsize;
+        }
+    }
+};
+
+}  // namespace details
+}  // namespace HighFive
diff --git a/packages/HighFive/include/highfive/h5easy_bits/H5Easy_xtensor.hpp b/packages/HighFive/include/highfive/h5easy_bits/H5Easy_xtensor.hpp
index 9b737f03b..ba27bc84a 100644
--- a/packages/HighFive/include/highfive/h5easy_bits/H5Easy_xtensor.hpp
+++ b/packages/HighFive/include/highfive/h5easy_bits/H5Easy_xtensor.hpp
@@ -20,6 +20,12 @@ namespace detail {
 
 template <typename T>
 struct io_impl<T, typename std::enable_if<xt::is_xexpression<T>::value>::type> {
+    inline static void assert_row_major(const File& file, const std::string& path, const T& data) {
+        if (data.layout() != xt::layout_type::row_major) {
+            throw detail::error(file, path, "Only row-major XTensor object are supported.");
+        }
+    }
+
     inline static std::vector<size_t> shape(const T& data) {
         return std::vector<size_t>(data.shape().cbegin(), data.shape().cend());
     }
@@ -28,6 +34,7 @@ struct io_impl<T, typename std::enable_if<xt::is_xexpression<T>::value>::type> {
                                const std::string& path,
                                const T& data,
                                const DumpOptions& options) {
+        assert_row_major(file, path, data);
         using value_type = typename std::decay_t<T>::value_type;
         DataSet dataset = initDataset<value_type>(file, path, shape(data), options);
         dataset.write_raw(data.data());
@@ -44,6 +51,7 @@ struct io_impl<T, typename std::enable_if<xt::is_xexpression<T>::value>::type> {
         DataSet dataset = file.getDataSet(path);
         std::vector<size_t> dims = dataset.getDimensions();
         T data = T::from_shape(dims);
+        assert_row_major(file, path, data);
         dataset.read_raw(data.data());
         return data;
     }
@@ -53,6 +61,7 @@ struct io_impl<T, typename std::enable_if<xt::is_xexpression<T>::value>::type> {
                                           const std::string& key,
                                           const T& data,
                                           const DumpOptions& options) {
+        assert_row_major(file, path, data);
         using value_type = typename std::decay_t<T>::value_type;
         Attribute attribute = initAttribute<value_type>(file, path, key, shape(data), options);
         attribute.write_raw(data.data());
@@ -73,6 +82,7 @@ struct io_impl<T, typename std::enable_if<xt::is_xexpression<T>::value>::type> {
         DataSpace dataspace = attribute.getSpace();
         std::vector<size_t> dims = dataspace.getDimensions();
         T data = T::from_shape(dims);
+        assert_row_major(file, path, data);
         attribute.read_raw(data.data());
         return data;
     }
diff --git a/packages/HighFive/include/highfive/span.hpp b/packages/HighFive/include/highfive/span.hpp
new file mode 100644
index 000000000..ab53319ee
--- /dev/null
+++ b/packages/HighFive/include/highfive/span.hpp
@@ -0,0 +1,102 @@
+/*
+ *  Copyright (c) 2024 Blue Brain Project
+ *
+ *  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)
+ *
+ */
+
+#pragma once
+
+#include "bits/H5Inspector_decl.hpp"
+#include "H5Exception.hpp"
+
+#include <span>
+
+namespace HighFive {
+namespace details {
+
+template <class T, std::size_t Extent>
+struct inspector<std::span<T, Extent>> {
+    using type = std::span<T, Extent>;
+    using value_type = unqualified_t<T>;
+    using base_type = typename inspector<value_type>::base_type;
+    using hdf5_type = typename inspector<value_type>::hdf5_type;
+
+    static constexpr size_t ndim = 1;
+    static constexpr size_t min_ndim = ndim + inspector<value_type>::min_ndim;
+    static constexpr size_t max_ndim = ndim + inspector<value_type>::max_ndim;
+
+    static constexpr bool is_trivially_copyable = std::is_trivially_copyable<value_type>::value &&
+                                                  inspector<value_type>::is_trivially_nestable;
+    static constexpr bool is_trivially_nestable = false;
+
+
+    static size_t getRank(const type& val) {
+        if (!val.empty()) {
+            return ndim + inspector<value_type>::getRank(val[0]);
+        } else {
+            return min_ndim;
+        }
+    }
+
+    static std::vector<size_t> getDimensions(const type& val) {
+        auto rank = getRank(val);
+        std::vector<size_t> sizes(rank, 1ul);
+        sizes[0] = val.size();
+        if (!val.empty()) {
+            auto s = inspector<value_type>::getDimensions(val[0]);
+            assert(s.size() + ndim == sizes.size());
+            for (size_t i = 0; i < s.size(); ++i) {
+                sizes[i + ndim] = s[i];
+            }
+        }
+        return sizes;
+    }
+
+    static void prepare(type& val, const std::vector<size_t>& expected_dims) {
+        auto actual_dims = getDimensions(val);
+        if (actual_dims.size() != expected_dims.size()) {
+            throw DataSpaceException("Mismatching rank.");
+        }
+
+        for (size_t i = 0; i < actual_dims.size(); ++i) {
+            if (actual_dims[i] != expected_dims[i]) {
+                throw DataSpaceException("Mismatching dimensions.");
+            }
+        }
+    }
+
+    static hdf5_type* data(type& val) {
+        return val.empty() ? nullptr : inspector<value_type>::data(val[0]);
+    }
+
+    static const hdf5_type* data(const type& val) {
+        return val.empty() ? nullptr : inspector<value_type>::data(val[0]);
+    }
+
+    template <class It>
+    static void serialize(const type& val, const std::vector<size_t>& dims, It m) {
+        if (!val.empty()) {
+            auto subdims = std::vector<size_t>(dims.begin() + ndim, dims.end());
+            size_t subsize = compute_total_size(subdims);
+            for (const auto& e: val) {
+                inspector<value_type>::serialize(e, subdims, m);
+                m += subsize;
+            }
+        }
+    }
+
+    template <class It>
+    static void unserialize(const It& vec_align, const std::vector<size_t>& dims, type& val) {
+        std::vector<size_t> subdims(dims.begin() + ndim, dims.end());
+        size_t subsize = compute_total_size(subdims);
+        for (size_t i = 0; i < dims[0]; ++i) {
+            inspector<value_type>::unserialize(vec_align + i * subsize, subdims, val[i]);
+        }
+    }
+};
+
+}  // namespace details
+}  // namespace HighFive
diff --git a/packages/HighFive/include/highfive/xtensor.hpp b/packages/HighFive/include/highfive/xtensor.hpp
new file mode 100644
index 000000000..729a1e349
--- /dev/null
+++ b/packages/HighFive/include/highfive/xtensor.hpp
@@ -0,0 +1,212 @@
+#pragma once
+
+#include "bits/H5Inspector_decl.hpp"
+#include "H5Exception.hpp"
+
+#include <xtensor/xtensor.hpp>
+#include <xtensor/xarray.hpp>
+#include <xtensor/xadapt.hpp>
+
+namespace HighFive {
+namespace details {
+
+template <class XTensor>
+struct xtensor_get_rank;
+
+template <typename T, size_t N, xt::layout_type L>
+struct xtensor_get_rank<xt::xtensor<T, N, L>> {
+    static constexpr size_t value = N;
+};
+
+template <class EC, size_t N, xt::layout_type L, class Tag>
+struct xtensor_get_rank<xt::xtensor_adaptor<EC, N, L, Tag>> {
+    static constexpr size_t value = N;
+};
+
+template <class Derived, class XTensorType, xt::layout_type L>
+struct xtensor_inspector_base {
+    using type = XTensorType;
+    using value_type = typename type::value_type;
+    using base_type = typename inspector<value_type>::base_type;
+    using hdf5_type = base_type;
+
+    static_assert(std::is_same<value_type, base_type>::value,
+                  "HighFive's XTensor support only works for scalar elements.");
+
+    static constexpr bool IsConstExprRowMajor = L == xt::layout_type::row_major;
+    static constexpr bool is_trivially_copyable = IsConstExprRowMajor &&
+                                                  std::is_trivially_copyable<value_type>::value &&
+                                                  inspector<value_type>::is_trivially_copyable;
+
+    static constexpr bool is_trivially_nestable = false;
+
+    static size_t getRank(const type& val) {
+        // Non-scalar elements are not supported.
+        return val.shape().size();
+    }
+
+    static const value_type& getAnyElement(const type& val) {
+        return val.unchecked(0);
+    }
+
+    static value_type& getAnyElement(type& val) {
+        return val.unchecked(0);
+    }
+
+    static std::vector<size_t> getDimensions(const type& val) {
+        auto shape = val.shape();
+        return {shape.begin(), shape.end()};
+    }
+
+    static void prepare(type& val, const std::vector<size_t>& dims) {
+        val.resize(Derived::shapeFromDims(dims));
+    }
+
+    static hdf5_type* data(type& val) {
+        if (!is_trivially_copyable) {
+            throw DataSetException("Invalid used of `inspector<XTensor>::data`.");
+        }
+
+        if (val.size() == 0) {
+            return nullptr;
+        }
+
+        return inspector<value_type>::data(getAnyElement(val));
+    }
+
+    static const hdf5_type* data(const type& val) {
+        if (!is_trivially_copyable) {
+            throw DataSetException("Invalid used of `inspector<XTensor>::data`.");
+        }
+
+        if (val.size() == 0) {
+            return nullptr;
+        }
+
+        return inspector<value_type>::data(getAnyElement(val));
+    }
+
+    static void serialize(const type& val, const std::vector<size_t>& dims, hdf5_type* m) {
+        // since we only support scalar types we know all dims belong to us.
+        size_t size = compute_total_size(dims);
+        xt::adapt(m, size, xt::no_ownership(), dims) = val;
+    }
+
+    static void unserialize(const hdf5_type* vec_align,
+                            const std::vector<size_t>& dims,
+                            type& val) {
+        // since we only support scalar types we know all dims belong to us.
+        size_t size = compute_total_size(dims);
+        val = xt::adapt(vec_align, size, xt::no_ownership(), dims);
+    }
+};
+
+template <class XTensorType, xt::layout_type L>
+struct xtensor_inspector
+    : public xtensor_inspector_base<xtensor_inspector<XTensorType, L>, XTensorType, L> {
+  private:
+    using super = xtensor_inspector_base<xtensor_inspector<XTensorType, L>, XTensorType, L>;
+
+  public:
+    using type = typename super::type;
+    using value_type = typename super::value_type;
+    using base_type = typename super::base_type;
+    using hdf5_type = typename super::hdf5_type;
+
+    static constexpr size_t ndim = xtensor_get_rank<XTensorType>::value;
+    static constexpr size_t min_ndim = ndim + inspector<value_type>::min_ndim;
+    static constexpr size_t max_ndim = ndim + inspector<value_type>::max_ndim;
+
+    static std::array<size_t, ndim> shapeFromDims(const std::vector<size_t>& dims) {
+        std::array<size_t, ndim> shape;
+        std::copy(dims.cbegin(), dims.cend(), shape.begin());
+        return shape;
+    }
+};
+
+template <class XArrayType, xt::layout_type L>
+struct xarray_inspector
+    : public xtensor_inspector_base<xarray_inspector<XArrayType, L>, XArrayType, L> {
+  private:
+    using super = xtensor_inspector_base<xarray_inspector<XArrayType, L>, XArrayType, L>;
+
+  public:
+    using type = typename super::type;
+    using value_type = typename super::value_type;
+    using base_type = typename super::base_type;
+    using hdf5_type = typename super::hdf5_type;
+
+    static constexpr size_t min_ndim = 0 + inspector<value_type>::min_ndim;
+    static constexpr size_t max_ndim = 1024 + inspector<value_type>::max_ndim;
+
+    static const std::vector<size_t>& shapeFromDims(const std::vector<size_t>& dims) {
+        return dims;
+    }
+};
+
+template <typename T, size_t N, xt::layout_type L>
+struct inspector<xt::xtensor<T, N, L>>: public xtensor_inspector<xt::xtensor<T, N, L>, L> {
+  private:
+    using super = xtensor_inspector<xt::xtensor<T, N, L>, L>;
+
+  public:
+    using type = typename super::type;
+    using value_type = typename super::value_type;
+    using base_type = typename super::base_type;
+    using hdf5_type = typename super::hdf5_type;
+};
+
+template <typename T, xt::layout_type L>
+struct inspector<xt::xarray<T, L>>: public xarray_inspector<xt::xarray<T, L>, L> {
+  private:
+    using super = xarray_inspector<xt::xarray<T, L>, L>;
+
+  public:
+    using type = typename super::type;
+    using value_type = typename super::value_type;
+    using base_type = typename super::base_type;
+    using hdf5_type = typename super::hdf5_type;
+};
+
+template <typename CT, class... S>
+struct inspector<xt::xview<CT, S...>>
+    : public xarray_inspector<xt::xview<CT, S...>, xt::layout_type::any> {
+  private:
+    using super = xarray_inspector<xt::xview<CT, S...>, xt::layout_type::any>;
+
+  public:
+    using type = typename super::type;
+    using value_type = typename super::value_type;
+    using base_type = typename super::base_type;
+    using hdf5_type = typename super::hdf5_type;
+};
+
+
+template <class EC, xt::layout_type L, class SC, class Tag>
+struct inspector<xt::xarray_adaptor<EC, L, SC, Tag>>
+    : public xarray_inspector<xt::xarray_adaptor<EC, L, SC, Tag>, xt::layout_type::any> {
+  private:
+    using super = xarray_inspector<xt::xarray_adaptor<EC, L, SC, Tag>, xt::layout_type::any>;
+
+  public:
+    using type = typename super::type;
+    using value_type = typename super::value_type;
+    using base_type = typename super::base_type;
+    using hdf5_type = typename super::hdf5_type;
+};
+
+template <class EC, size_t N, xt::layout_type L, class Tag>
+struct inspector<xt::xtensor_adaptor<EC, N, L, Tag>>
+    : public xtensor_inspector<xt::xtensor_adaptor<EC, N, L, Tag>, xt::layout_type::any> {
+  private:
+    using super = xtensor_inspector<xt::xtensor_adaptor<EC, N, L, Tag>, xt::layout_type::any>;
+
+  public:
+    using type = typename super::type;
+    using value_type = typename super::value_type;
+    using base_type = typename super::base_type;
+    using hdf5_type = typename super::hdf5_type;
+};
+
+}  // namespace details
+}  // namespace HighFive
diff --git a/packages/HighFive/src/examples/CMakeLists.txt b/packages/HighFive/src/examples/CMakeLists.txt
index 5a1384557..5f4b4edf8 100644
--- a/packages/HighFive/src/examples/CMakeLists.txt
+++ b/packages/HighFive/src/examples/CMakeLists.txt
@@ -1,5 +1,6 @@
 set(core_examples
   ${CMAKE_CURRENT_SOURCE_DIR}/compound_types.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/broadcasting_arrays.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/create_attribute_string_integer.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/create_dataset_double.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/create_datatype.cpp
@@ -18,6 +19,10 @@ set(core_examples
   ${CMAKE_CURRENT_SOURCE_DIR}/select_partial_dataset_cpp11.cpp
 )
 
+set(span_examples
+  ${CMAKE_CURRENT_SOURCE_DIR}/read_write_std_span.cpp
+)
+
 set(easy_examples
   ${CMAKE_CURRENT_SOURCE_DIR}/easy_attribute.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/easy_dumpoptions.cpp
@@ -52,7 +57,6 @@ set(half_float_examples
 function(compile_example example_source)
   get_filename_component(example_filename ${example_source} NAME)
   string(REPLACE ".cpp" "_bin" example_name ${example_filename})
-  message("example_name: ${example_name}")
 
   add_executable(${example_name} ${example_source})
   target_link_libraries(${example_name} PUBLIC HighFive HighFiveWarnings HighFiveFlags)
@@ -70,6 +74,12 @@ foreach(example_source ${easy_examples})
   compile_example(${example_source})
 endforeach()
 
+if(HIGHFIVE_TEST_SPAN)
+  foreach(example_source ${span_examples})
+    compile_example(${example_source})
+  endforeach()
+endif()
+
 if(HIGHFIVE_TEST_BOOST)
   foreach(example_source ${boost_examples})
     compile_example(${example_source} HighFiveBoostDependency)
diff --git a/packages/HighFive/src/examples/broadcasting_arrays.cpp b/packages/HighFive/src/examples/broadcasting_arrays.cpp
new file mode 100644
index 000000000..3684e17b0
--- /dev/null
+++ b/packages/HighFive/src/examples/broadcasting_arrays.cpp
@@ -0,0 +1,50 @@
+#include <highfive/highfive.hpp>
+
+// This example explains how to read a dataset with some shape into an array of
+// some other shape. Naturally, this only makes sense if the number of elements
+// doesn't change.
+//
+// Note that due to how HDF5 works, writing from one shape into some other
+// shape is expected to work automatically.
+//
+// Same is true for reading. However, HighFive also allocates memory, the array
+// into which the data is read is forced to have the same shape as the
+// memspace. When performing selections it can often happen that one selects a
+// one-dimensional slice from a higher dimensional array.  In this case we want
+// to be able to read into a one dimensional array, e.g. `std::vector<double>`.
+//
+// Broadcasting is a common technique for hiding benign differences in
+// dimensionality. In HighFive we suggest to either "squeeze" or "reshape" the
+// memspace, rather than broadcasting. This example demonstrates the required
+// syntax.
+//
+// Note: These techniques can also be used for general hyperslabs which the
+// user knows are in fact hypercubes, i.e. regular.
+//
+// Note: HighFive v2 has support for broadcasting; but because it's quirky,
+// less powerful than the demonstrated technique, relied on a compile-time
+// constant rank and is quite complex to maintain, the functionality was
+// removed from v3.
+
+using namespace HighFive;
+
+int main(void) {
+    File file("broadcasting_arrays.h5", File::Truncate);
+
+    std::vector<size_t> dims{3, 1};
+    std::vector<double> values{1.0, 2.0, 3.0};
+
+    auto dset = file.createDataSet("dset", DataSpace(dims), create_datatype<double>());
+
+    // Note that because `values` is one-dimensional, we can't write it
+    // to a dataset of dimensions `[3, 1]` directly. Instead we use:
+    dset.squeezeMemSpace({1}).write(values);
+
+    // When reading, (re-)allocation might occur. The shape to be allocated is
+    // the dimensions of the memspace. Therefore, one might want to either remove
+    // an axis:
+    dset.squeezeMemSpace({1}).read(values);
+
+    // or reshape the memspace:
+    dset.reshapeMemSpace({3}).read(values);
+}
diff --git a/packages/HighFive/src/examples/read_write_std_span.cpp b/packages/HighFive/src/examples/read_write_std_span.cpp
new file mode 100644
index 000000000..72465c46d
--- /dev/null
+++ b/packages/HighFive/src/examples/read_write_std_span.cpp
@@ -0,0 +1,56 @@
+/*
+ *  Copyright (c), 2024, Blue Brain Project
+ *
+ *  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::span`. An `std::span` is a pointer
+// with a size.
+
+#include <string>
+#include <vector>
+
+#include <highfive/highfive.hpp>
+
+#include <highfive/span.hpp>
+
+int main(void) {
+    using namespace HighFive;
+
+    std::string file_name = "read_write_span.h5";
+    std::string dataset_name = "array";
+
+    File file(file_name, File::Truncate);
+
+    // Let's write to file.
+    {
+        // Assume we have one-dimensional data in some unsupported format (we
+        // use `std::vector` for simplicity). Further, assume that the data is
+        // stored contiguously. Then one can create an `std::span`.
+        std::vector<double> values{1.0, 2.0, 3.0};
+        auto view = std::span<double>(values.data(), values.size());
+
+        // Given the span, HighFive can deduce the shape of the dataset. Hence,
+        // spans are fully supported when writing. For example:
+        auto dataset = file.createDataSet(dataset_name, view);
+    }
+
+    // Let's read from file.
+    {
+        auto dataset = file.getDataSet(dataset_name);
+
+        // Since spans are views, HighFive can't (or wont) allocate memory.
+        // Instead one must preallocate memory and then create a span for that
+        // memory:
+        auto values = std::vector<double>(dataset.getElementCount());
+        auto view = std::span<double>(values.data(), values.size());
+
+        // ... now we can read into the preallocated memory:
+        dataset.read(view);
+    }
+
+    return 0;
+}
diff --git a/packages/HighFive/tests/unit/CMakeLists.txt b/packages/HighFive/tests/unit/CMakeLists.txt
index c5a07e8e8..c8835ba34 100644
--- a/packages/HighFive/tests/unit/CMakeLists.txt
+++ b/packages/HighFive/tests/unit/CMakeLists.txt
@@ -6,7 +6,7 @@ if(MSVC)
 endif()
 
 ## Base tests
-foreach(test_name tests_high_five_base tests_high_five_multi_dims tests_high_five_easy test_all_types test_high_five_selection tests_high_five_data_type test_legacy)
+foreach(test_name tests_high_five_base tests_high_five_multi_dims tests_high_five_easy test_all_types test_high_five_selection tests_high_five_data_type test_empty_arrays test_legacy test_opencv test_string test_xtensor)
   add_executable(${test_name} "${test_name}.cpp")
   target_link_libraries(${test_name} HighFive HighFiveWarnings HighFiveFlags Catch2::Catch2WithMain)
   target_link_libraries(${test_name} HighFiveOptionalDependencies)
@@ -14,7 +14,7 @@ foreach(test_name tests_high_five_base tests_high_five_multi_dims tests_high_fiv
   catch_discover_tests(${test_name})
 endforeach()
 
-if(HIGHFIVE_PARALLEL_HDF5)
+if(HDF5_IS_PARALLEL)
   set(tests_parallel_src "tests_high_five_parallel.cpp")
 
   ## parallel MPI tests
@@ -42,26 +42,43 @@ if(HIGHFIVE_PARALLEL_HDF5)
   set(_CATCH_DISCOVER_TESTS_SCRIPT "${original_catch_script}")
 endif()
 
-option(HIGHFIVE_TEST_SINGLE_INCLUDES "Enable testing single includes" FALSE)
+# Test that each public header is self-sufficient. This is done by
+# creating a file for each header, that only includes the header. The
+# test succeeds if it compiles.
+file(GLOB public_headers LIST_DIRECTORIES false RELATIVE ${PROJECT_SOURCE_DIR}/include CONFIGURE_DEPENDS ${PROJECT_SOURCE_DIR}/include/highfive/*.hpp)
+foreach(PUBLIC_HEADER ${public_headers})
+    if(PUBLIC_HEADER STREQUAL "highfive/span.hpp" AND NOT HIGHFIVE_TEST_SPAN)
+      continue()
+    endif()
 
-if(HIGHFIVE_TEST_SINGLE_INCLUDES)
-  file(GLOB CONFIGURE_DEPENDS public_headers LIST_DIRECTORIES false RELATIVE ${PROJECT_SOURCE_DIR}/include ${PROJECT_SOURCE_DIR}/include/highfive/*.hpp)
-    foreach(PUBLIC_HEADER ${public_headers})
-        if(PUBLIC_HEADER STREQUAL "highfive/boost.hpp" AND NOT HIGHFIVE_TEST_BOOST)
-          continue()
-        endif()
+    if(PUBLIC_HEADER STREQUAL "highfive/boost.hpp" AND NOT HIGHFIVE_TEST_BOOST)
+      continue()
+    endif()
 
-        if(PUBLIC_HEADER STREQUAL "highfive/half_float.hpp" AND NOT HIGHFIVE_TEST_HALF_FLOAT)
-          continue()
-        endif()
+    if(PUBLIC_HEADER STREQUAL "highfive/half_float.hpp" AND NOT HIGHFIVE_TEST_HALF_FLOAT)
+      continue()
+    endif()
 
-        if(PUBLIC_HEADER STREQUAL "highfive/eigen.hpp" AND NOT HIGHFIVE_TEST_EIGEN)
-          continue()
-        endif()
+    if(PUBLIC_HEADER STREQUAL "highfive/eigen.hpp" AND NOT HIGHFIVE_TEST_EIGEN)
+      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")
-        target_link_libraries("tests_include_${CLASS_NAME}" HighFive HighFiveWarnings HighFiveFlags)
-    endforeach()
-endif()
+    if(PUBLIC_HEADER STREQUAL "highfive/opencv.hpp" AND NOT HIGHFIVE_TEST_OPENCV)
+      continue()
+    endif()
+
+    if(PUBLIC_HEADER STREQUAL "highfive/xtensor.hpp" AND NOT HIGHFIVE_TEST_XTENSOR)
+      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")
+    target_link_libraries(
+        "tests_include_${CLASS_NAME}" PUBLIC
+        HighFive
+        HighFiveWarnings
+        HighFiveFlags
+        HighFiveOptionalDependencies
+    )
+endforeach()
diff --git a/packages/HighFive/tests/unit/data_generator.hpp b/packages/HighFive/tests/unit/data_generator.hpp
index 2964bf9fd..d513c3420 100644
--- a/packages/HighFive/tests/unit/data_generator.hpp
+++ b/packages/HighFive/tests/unit/data_generator.hpp
@@ -17,11 +17,20 @@
 #include <highfive/eigen.hpp>
 #endif
 
+#ifdef HIGHFIVE_TEST_SPAN
+#include <highfive/span.hpp>
+#endif
+
+#ifdef HIGHFIVE_TEST_XTENSOR
+#include <highfive/xtensor.hpp>
+#endif
+
 
 namespace HighFive {
 namespace testing {
 
-std::vector<size_t> lstrip(const std::vector<size_t>& indices, size_t n) {
+template <class Dims>
+std::vector<size_t> lstrip(const Dims& indices, size_t n) {
     std::vector<size_t> subindices(indices.size() - n);
     for (size_t i = 0; i < subindices.size(); ++i) {
         subindices[i] = indices[i + n];
@@ -30,7 +39,8 @@ std::vector<size_t> lstrip(const std::vector<size_t>& indices, size_t n) {
     return subindices;
 }
 
-size_t ravel(std::vector<size_t>& indices, const std::vector<size_t> dims) {
+template <class Dims>
+size_t ravel(std::vector<size_t>& indices, const Dims& dims) {
     size_t rank = dims.size();
     size_t linear_index = 0;
     size_t ld = 1;
@@ -43,7 +53,8 @@ size_t ravel(std::vector<size_t>& indices, const std::vector<size_t> dims) {
     return linear_index;
 }
 
-std::vector<size_t> unravel(size_t flat_index, const std::vector<size_t> dims) {
+template <class Dims>
+std::vector<size_t> unravel(size_t flat_index, const Dims& dims) {
     size_t rank = dims.size();
     size_t ld = 1;
     std::vector<size_t> indices(rank);
@@ -56,7 +67,8 @@ std::vector<size_t> unravel(size_t flat_index, const std::vector<size_t> dims) {
     return indices;
 }
 
-static size_t flat_size(const std::vector<size_t>& dims) {
+template <class Dims>
+static size_t flat_size(const Dims& dims) {
     size_t n = 1;
     for (auto d: dims) {
         n *= d;
@@ -75,6 +87,7 @@ struct ScalarContainerTraits {
     using base_type = T;
 
     static constexpr bool is_view = false;
+    static constexpr size_t rank = 0;
 
     static void set(container_type& array, std::vector<size_t> /* indices */, base_type value) {
         array = value;
@@ -116,6 +129,7 @@ struct ContainerTraits<std::vector<bool>> {
     using base_type = bool;
 
     static constexpr bool is_view = false;
+    static constexpr size_t rank = 1;
 
     static void set(container_type& array,
                     const std::vector<size_t>& indices,
@@ -150,6 +164,7 @@ struct STLLikeContainerTraits {
     using base_type = typename ContainerTraits<value_type>::base_type;
 
     static constexpr bool is_view = ContainerTraits<value_type>::is_view;
+    static constexpr size_t rank = 1 + ContainerTraits<value_type>::rank;
 
     static void set(container_type& array,
                     const std::vector<size_t>& indices,
@@ -221,6 +236,53 @@ struct ContainerTraits<std::array<T, N>>: public STLLikeContainerTraits<std::arr
     }
 };
 
+
+#ifdef HIGHFIVE_TEST_SPAN
+template <class T, std::size_t Extent>
+struct ContainerTraits<std::span<T, Extent>>: public STLLikeContainerTraits<std::span<T, Extent>> {
+  private:
+    using super = STLLikeContainerTraits<std::span<T, Extent>>;
+
+  public:
+    using container_type = typename super::container_type;
+    using value_type = typename super::value_type;
+    using base_type = typename super::base_type;
+
+    static constexpr bool is_view = true;
+
+    static container_type allocate(const std::vector<size_t>& dims) {
+        size_t n_elements = dims[0];
+        value_type* ptr = new value_type[n_elements];
+
+        container_type array = container_type(ptr, n_elements);
+
+        for (size_t i = 0; i < n_elements; ++i) {
+            auto element = ContainerTraits<value_type>::allocate(lstrip(dims, 1));
+            ContainerTraits<value_type>::assign(array[i], element);
+        }
+
+        return array;
+    }
+
+    static void deallocate(container_type& array, const std::vector<size_t>& dims) {
+        size_t n_elements = dims[0];
+        for (size_t i = 0; i < n_elements; ++i) {
+            ContainerTraits<value_type>::deallocate(array[i], lstrip(dims, 1));
+        }
+
+        delete[] array.data();
+    }
+
+    static void sanitize_dims(std::vector<size_t>& dims, size_t axis) {
+        if (Extent != std::dynamic_extent) {
+            dims[axis] = Extent;
+            ContainerTraits<value_type>::sanitize_dims(dims, axis + 1);
+        }
+    }
+};
+#endif
+
+
 // -- Boost  -------------------------------------------------------------------
 #ifdef HIGHFIVE_TEST_BOOST
 template <class T, size_t n>
@@ -230,6 +292,7 @@ struct ContainerTraits<boost::multi_array<T, n>> {
     using base_type = typename ContainerTraits<value_type>::base_type;
 
     static constexpr bool is_view = ContainerTraits<value_type>::is_view;
+    static constexpr size_t rank = n + ContainerTraits<value_type>::rank;
 
     static void set(container_type& array,
                     const std::vector<size_t>& indices,
@@ -282,6 +345,7 @@ struct ContainerTraits<boost::numeric::ublas::matrix<T>> {
     using base_type = typename ContainerTraits<value_type>::base_type;
 
     static constexpr bool is_view = ContainerTraits<value_type>::is_view;
+    static constexpr size_t rank = 2 + ContainerTraits<value_type>::rank;
 
     static void set(container_type& array,
                     const std::vector<size_t>& indices,
@@ -332,6 +396,7 @@ struct ContainerTraits<boost::numeric::ublas::matrix<T>> {
 
 #endif
 
+// -- Eigen  -------------------------------------------------------------------
 #if HIGHFIVE_TEST_EIGEN
 
 template <typename EigenType>
@@ -341,6 +406,7 @@ struct EigenContainerTraits {
     using base_type = typename ContainerTraits<value_type>::base_type;
 
     static constexpr bool is_view = ContainerTraits<value_type>::is_view;
+    static constexpr size_t rank = 2 + ContainerTraits<value_type>::rank;
 
     static void set(container_type& array,
                     const std::vector<size_t>& indices,
@@ -468,6 +534,88 @@ struct ContainerTraits<Eigen::Map<PlainObjectType, MapOptions>>
 };
 
 
+#endif
+
+// -- XTensor  -----------------------------------------------------------------
+
+#if HIGHFIVE_TEST_XTENSOR
+template <typename XTensorType, size_t Rank>
+struct XTensorContainerTraits {
+    using container_type = XTensorType;
+    using value_type = typename container_type::value_type;
+    using base_type = typename ContainerTraits<value_type>::base_type;
+
+    static constexpr size_t rank = Rank;
+    static constexpr bool is_view = ContainerTraits<value_type>::is_view;
+
+    static void set(container_type& array,
+                    const std::vector<size_t>& indices,
+                    const base_type& value) {
+        std::vector<size_t> local_indices(indices.begin(), indices.begin() + rank);
+        return ContainerTraits<value_type>::set(array[local_indices], lstrip(indices, rank), value);
+    }
+
+    static base_type get(const container_type& array, const std::vector<size_t>& indices) {
+        std::vector<size_t> local_indices(indices.begin(), indices.begin() + rank);
+        return ContainerTraits<value_type>::get(array[local_indices], lstrip(indices, rank));
+    }
+
+    static void assign(container_type& dst, const container_type& src) {
+        dst = src;
+    }
+
+    static container_type allocate(const std::vector<size_t>& dims) {
+        const auto& local_dims = details::inspector<XTensorType>::shapeFromDims(dims);
+        auto array = container_type(local_dims);
+
+        size_t n_elements = flat_size(local_dims);
+        for (size_t i = 0; i < n_elements; ++i) {
+            auto element = ContainerTraits<value_type>::allocate(lstrip(dims, rank));
+            set(array, unravel(i, local_dims), element);
+        }
+
+        return array;
+    }
+
+    static void deallocate(container_type& array, const std::vector<size_t>& dims) {
+        auto local_dims = std::vector<size_t>(dims.begin(), dims.begin() + rank);
+        size_t n_elements = flat_size(local_dims);
+        for (size_t i_flat = 0; i_flat < n_elements; ++i_flat) {
+            auto indices = unravel(i_flat, local_dims);
+            std::vector<size_t> local_indices(indices.begin(), indices.begin() + rank);
+            ContainerTraits<value_type>::deallocate(array[local_indices], lstrip(dims, rank));
+        }
+    }
+
+    static void sanitize_dims(std::vector<size_t>& dims, size_t axis) {
+        ContainerTraits<value_type>::sanitize_dims(dims, axis + rank);
+    }
+};
+
+template <class T, size_t rank, xt::layout_type layout>
+struct ContainerTraits<xt::xtensor<T, rank, layout>>
+    : public XTensorContainerTraits<xt::xtensor<T, rank, layout>, rank> {
+  private:
+    using super = XTensorContainerTraits<xt::xtensor<T, rank, layout>, rank>;
+
+  public:
+    using container_type = typename super::container_type;
+    using value_type = typename super::value_type;
+    using base_type = typename super::base_type;
+};
+
+template <class T, xt::layout_type layout>
+struct ContainerTraits<xt::xarray<T, layout>>
+    : public XTensorContainerTraits<xt::xarray<T, layout>, 2> {
+  private:
+    using super = XTensorContainerTraits<xt::xarray<T, layout>, 2>;
+
+  public:
+    using container_type = typename super::container_type;
+    using value_type = typename super::value_type;
+    using base_type = typename super::base_type;
+};
+
 #endif
 
 template <class T, class C>
@@ -578,11 +726,12 @@ struct MultiDimVector<T, 0> {
 template <class Container>
 class DataGenerator {
   public:
-    constexpr static size_t rank = details::inspector<Container>::recursive_ndim;
     using traits = ContainerTraits<Container>;
     using base_type = typename traits::base_type;
     using container_type = Container;
 
+    constexpr static size_t rank = traits::rank;
+
   public:
     static container_type allocate(const std::vector<size_t>& dims) {
         return traits::allocate(dims);
diff --git a/packages/HighFive/tests/unit/supported_types.hpp b/packages/HighFive/tests/unit/supported_types.hpp
index 75e442c60..5f1bf6453 100644
--- a/packages/HighFive/tests/unit/supported_types.hpp
+++ b/packages/HighFive/tests/unit/supported_types.hpp
@@ -34,6 +34,14 @@ struct STDArray {
     using type = std::array<typename C::template type<T>, n>;
 };
 
+#ifdef HIGHFIVE_TEST_SPAN
+template <class C = type_identity>
+struct STDSpan {
+    template <class T>
+    using type = std::span<typename C::template type<T>>;
+};
+#endif
+
 #ifdef HIGHFIVE_TEST_BOOST
 template <size_t n, class C = type_identity>
 struct BoostMultiArray {
@@ -74,6 +82,20 @@ struct EigenMapMatrix {
 };
 #endif
 
+#ifdef HIGHFIVE_TEST_XTENSOR
+template <size_t rank, xt::layout_type layout, class C = type_identity>
+struct XTensor {
+    template <class T>
+    using type = xt::xtensor<typename C::template type<T>, rank, layout>;
+};
+
+template <xt::layout_type layout, class C = type_identity>
+struct XArray {
+    template <class T>
+    using type = xt::xarray<typename C::template type<T>, layout>;
+};
+#endif
+
 template <class C, class Tuple>
 struct ContainerProduct;
 
@@ -150,6 +172,23 @@ using supported_array_types = typename ConcatenateTuples<
   typename ContainerProduct<STDArray<7, EigenMatrix<3, 5, Eigen::RowMajor>>, scalar_types_eigen>::type,
   typename ContainerProduct<STDArray<7, EigenArray<Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>>, scalar_types_eigen>::type,
   std::tuple<std::array<Eigen::VectorXd, 7>>,
+#endif
+#ifdef HIGHFIVE_TEST_SPAN
+  typename ContainerProduct<STDSpan<>, all_scalar_types>::type,
+  typename ContainerProduct<STDVector<STDSpan<>>, some_scalar_types>::type,
+  typename ContainerProduct<STDArray<7, STDSpan<>>, some_scalar_types>::type,
+  typename ContainerProduct<STDSpan<STDVector<>>, some_scalar_types>::type,
+  typename ContainerProduct<STDSpan<STDArray<3>>, some_scalar_types>::type,
+#endif
+#ifdef HIGHFIVE_TEST_XTENSOR
+  typename ContainerProduct<XTensor<3, xt::layout_type::row_major>, scalar_types_eigen>::type,
+  typename ContainerProduct<STDVector<XTensor<3, xt::layout_type::row_major>>, scalar_types_eigen>::type,
+  typename ContainerProduct<STDArray<5, XTensor<3, xt::layout_type::row_major>>, scalar_types_eigen>::type,
+  typename ContainerProduct<XTensor<3, xt::layout_type::column_major>, scalar_types_eigen>::type,
+  typename ContainerProduct<XArray<xt::layout_type::row_major>, scalar_types_eigen>::type,
+  typename ContainerProduct<STDVector<XArray<xt::layout_type::row_major>>, scalar_types_eigen>::type,
+  typename ContainerProduct<STDArray<5, XArray<xt::layout_type::row_major>>, scalar_types_eigen>::type,
+  typename ContainerProduct<XArray<xt::layout_type::column_major>, scalar_types_eigen>::type,
 #endif
   typename ContainerProduct<STDVector<>, all_scalar_types>::type,
   typename ContainerProduct<STDVector<STDVector<>>, some_scalar_types>::type,
diff --git a/packages/HighFive/tests/unit/test_empty_arrays.cpp b/packages/HighFive/tests/unit/test_empty_arrays.cpp
new file mode 100644
index 000000000..ea447ffaf
--- /dev/null
+++ b/packages/HighFive/tests/unit/test_empty_arrays.cpp
@@ -0,0 +1,248 @@
+/*
+ *  Copyright (c), 2017-2024, Blue Brain Project - EPFL
+ *
+ *  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)
+ *
+ */
+#include <string>
+#include <vector>
+
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/catch_template_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_vector.hpp>
+
+#include <highfive/highfive.hpp>
+#include "tests_high_five.hpp"
+#include "create_traits.hpp"
+
+#ifdef HIGHFIVE_TEST_BOOST
+#include <highfive/boost.hpp>
+#endif
+
+#ifdef HIGHFIVE_TEST_EIGEN
+#include <highfive/eigen.hpp>
+#endif
+
+#ifdef HIGHFIVE_TEST_SPAN
+#include <highfive/span.hpp>
+#endif
+
+using namespace HighFive;
+using Catch::Matchers::Equals;
+
+
+template <int n_dim>
+struct CreateEmptyVector;
+
+template <>
+struct CreateEmptyVector<1> {
+    using container_type = std::vector<int>;
+
+    static container_type create(const std::vector<size_t>& dims) {
+        return container_type(dims[0], 2);
+    }
+};
+
+template <int n_dim>
+struct CreateEmptyVector {
+    using container_type = std::vector<typename CreateEmptyVector<n_dim - 1>::container_type>;
+
+    static container_type create(const std::vector<size_t>& dims) {
+        auto subdims = std::vector<size_t>(dims.begin() + 1, dims.end());
+        return container_type(dims[0], CreateEmptyVector<n_dim - 1>::create(subdims));
+    }
+};
+
+#ifdef HIGHFIVE_TEST_BOOST
+template <int n_dim>
+struct CreateEmptyBoostMultiArray {
+    using container_type = boost::multi_array<int, static_cast<long unsigned>(n_dim)>;
+
+    static container_type create(const std::vector<size_t>& dims) {
+        auto container = container_type(dims);
+
+        auto raw_data = std::vector<int>(compute_total_size(dims));
+        container.assign(raw_data.begin(), raw_data.end());
+
+        return container;
+    }
+};
+#endif
+
+
+#ifdef HIGHFIVE_TEST_EIGEN
+struct CreateEmptyEigenVector {
+    using container_type = Eigen::VectorXi;
+
+    static container_type create(const std::vector<size_t>& dims) {
+        return container_type::Constant(int(dims[0]), 2);
+    }
+};
+
+struct CreateEmptyEigenMatrix {
+    using container_type = Eigen::MatrixXi;
+
+    static container_type create(const std::vector<size_t>& dims) {
+        return container_type::Constant(int(dims[0]), int(dims[1]), 2);
+    }
+};
+#endif
+
+template <class Container>
+void check_empty_dimensions(const Container& container, const std::vector<size_t>& expected_dims) {
+    auto deduced_dims = details::inspector<Container>::getDimensions(container);
+
+    REQUIRE(expected_dims.size() == deduced_dims.size());
+
+    // The dims after hitting the first `0` are finicky. We allow those to be deduced as either `1`
+    // or what the original dims said. The `1` allows broadcasting, the "same as original" enables
+    // statically sized objects, which conceptually have dims, even if there's no object.
+    bool allow_one = false;
+    for (size_t i = 0; i < expected_dims.size(); ++i) {
+        REQUIRE(((expected_dims[i] == deduced_dims[i]) || (allow_one && (deduced_dims[i] == 1ul))));
+
+        if (expected_dims[i] == 0) {
+            allow_one = true;
+        }
+    }
+}
+
+template <class CreateContainer>
+void check_empty_dimensions(const std::vector<size_t>& dims) {
+    auto input_data = CreateContainer::create(dims);
+    check_empty_dimensions(input_data, dims);
+}
+
+template <class ReadWriteInterface, class CreateContainer>
+void check_empty_read_write_cycle(const std::vector<size_t>& dims) {
+    using container_type = typename CreateContainer::container_type;
+
+    const std::string file_name("h5_empty_attr.h5");
+    const std::string dataset_name("dset");
+    File file(file_name, File::Truncate);
+
+    auto input_data = CreateContainer::create(dims);
+    ReadWriteInterface::create(file, dataset_name, input_data);
+
+    SECTION("read; one-dimensional vector (empty)") {
+        auto output_data = CreateEmptyVector<1>::create({0ul});
+
+        ReadWriteInterface::get(file, dataset_name).reshapeMemSpace({0ul}).read(output_data);
+        check_empty_dimensions(output_data, {0ul});
+    }
+
+    SECTION("read; pre-allocated (empty)") {
+        auto output_data = CreateContainer::create(dims);
+        ReadWriteInterface::get(file, dataset_name).reshapeMemSpace(dims).read(output_data);
+
+        check_empty_dimensions(output_data, dims);
+    }
+
+    SECTION("read; pre-allocated (oversized)") {
+        auto oversize_dims = std::vector<size_t>(dims.size(), 2ul);
+        auto output_data = CreateContainer::create(oversize_dims);
+        ReadWriteInterface::get(file, dataset_name).reshapeMemSpace(dims).read(output_data);
+
+        check_empty_dimensions(output_data, dims);
+    }
+
+    SECTION("read; auto-allocated") {
+        auto output_data = ReadWriteInterface::get(file, dataset_name)
+                               .reshapeMemSpace(dims)
+                               .template read<container_type>();
+        check_empty_dimensions(output_data, dims);
+    }
+}
+
+template <class CreateContainer>
+void check_empty_dataset(const std::vector<size_t>& dims) {
+    check_empty_read_write_cycle<testing::DataSetCreateTraits, CreateContainer>(dims);
+}
+
+template <class CreateContainer>
+void check_empty_attribute(const std::vector<size_t>& dims) {
+    check_empty_read_write_cycle<testing::AttributeCreateTraits, CreateContainer>(dims);
+}
+
+template <class CreateContainer>
+void check_empty_everything(const std::vector<size_t>& dims) {
+    SECTION("Empty dimensions") {
+        check_empty_dimensions<CreateContainer>(dims);
+    }
+
+    SECTION("Empty datasets") {
+        check_empty_dataset<CreateContainer>(dims);
+    }
+
+    SECTION("Empty attribute") {
+        check_empty_attribute<CreateContainer>(dims);
+    }
+}
+
+#ifdef HIGHFIVE_TEST_EIGEN
+template <int ndim>
+void check_empty_eigen(const std::vector<size_t>&) {}
+
+template <>
+void check_empty_eigen<1>(const std::vector<size_t>& dims) {
+    SECTION("Eigen::Vector") {
+        check_empty_everything<CreateEmptyEigenVector>({dims[0], 1ul});
+    }
+}
+
+template <>
+void check_empty_eigen<2>(const std::vector<size_t>& dims) {
+    SECTION("Eigen::Matrix") {
+        check_empty_everything<CreateEmptyEigenMatrix>(dims);
+    }
+}
+#endif
+
+template <int ndim>
+void check_empty(const std::vector<size_t>& dims) {
+    REQUIRE(dims.size() == ndim);
+
+    SECTION("std::vector") {
+        check_empty_everything<CreateEmptyVector<ndim>>(dims);
+    }
+
+#ifdef HIGHFIVE_TEST_BOOST
+    SECTION("boost::multi_array") {
+        check_empty_everything<CreateEmptyBoostMultiArray<ndim>>(dims);
+    }
+#endif
+
+#ifdef HIGHFIVE_TEST_EIGEN
+    check_empty_eigen<ndim>(dims);
+#endif
+}
+
+TEST_CASE("Empty arrays") {
+    SECTION("one-dimensional") {
+        check_empty<1>({0ul});
+    }
+
+    SECTION("two-dimensional") {
+        std::vector<std::vector<size_t>> testcases{{0ul, 1ul}, {1ul, 0ul}};
+
+        for (const auto& dims: testcases) {
+            SECTION(details::format_vector(dims)) {
+                check_empty<2>(dims);
+            }
+        }
+    }
+
+    SECTION("three-dimensional") {
+        std::vector<std::vector<size_t>> testcases{{0ul, 1ul, 1ul},
+                                                   {1ul, 1ul, 0ul},
+                                                   {1ul, 0ul, 1ul}};
+
+        for (const auto& dims: testcases) {
+            SECTION(details::format_vector(dims)) {
+                check_empty<3>(dims);
+            }
+        }
+    }
+}
diff --git a/packages/HighFive/tests/unit/test_legacy.cpp b/packages/HighFive/tests/unit/test_legacy.cpp
index 7d7e67f26..698131473 100644
--- a/packages/HighFive/tests/unit/test_legacy.cpp
+++ b/packages/HighFive/tests/unit/test_legacy.cpp
@@ -36,3 +36,39 @@ TEST_CASE("HighFiveReadWriteConsts") {
         }
     }
 }
+
+TEST_CASE("Array of char pointers") {
+    // Currently, serializing an `std::vector<char*>` as
+    // fixed or variable length strings doesn't work.
+    //
+    // This isn't a test of correctness. Rather it asserts the fact that
+    // something doesn't work in HighFive. Knowing it doesn't work is useful
+    // for developers, but could change in the future.
+
+    const std::string file_name = "vector_char_pointer.h5";
+
+    File file(file_name, File::Truncate);
+
+    size_t n_strings = 3;
+    size_t n_chars = 4;
+    char storage[3][4] = {"foo", "bar", "000"};
+    auto strings = std::vector<char*>(n_strings);
+
+    for (size_t i = 0; i < n_strings; ++i) {
+        strings[i] = static_cast<char*>(storage[i]);
+    }
+
+    auto filespace = DataSpace({n_strings});
+
+    SECTION("fixed length") {
+        auto datatype = FixedLengthStringType(n_chars, StringPadding::NullTerminated);
+        auto dset = file.createDataSet("dset", filespace, datatype);
+        REQUIRE_THROWS(dset.write(strings));
+    }
+
+    SECTION("variable length") {
+        auto datatype = VariableLengthStringType();
+        auto dset = file.createDataSet("dset", filespace, datatype);
+        REQUIRE_THROWS(dset.write(strings));
+    }
+}
diff --git a/packages/HighFive/tests/unit/test_opencv.cpp b/packages/HighFive/tests/unit/test_opencv.cpp
new file mode 100644
index 000000000..b27c06daa
--- /dev/null
+++ b/packages/HighFive/tests/unit/test_opencv.cpp
@@ -0,0 +1,59 @@
+/*
+ *  Copyright (c), 2024, Blue Brain Project - EPFL
+ *
+ *  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)
+ *
+ */
+
+#if HIGHFIVE_TEST_OPENCV
+
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/catch_template_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_vector.hpp>
+
+#include <highfive/highfive.hpp>
+#include <highfive/experimental/opencv.hpp>
+#include "tests_high_five.hpp"
+#include "create_traits.hpp"
+
+using namespace HighFive;
+using Catch::Matchers::Equals;
+
+TEST_CASE("OpenCV") {
+    auto file = File("rw_opencv.h5", File::Truncate);
+
+    auto a = cv::Mat_<double>(3, 5);
+    auto dset = file.createDataSet("a", a);
+    auto b = dset.read<cv::Mat_<double>>();
+    REQUIRE(a(0, 0) == b(0, 0));
+
+    auto va = std::vector<cv::Mat_<double>>(7, cv::Mat_<double>(3, 5));
+    auto vdset = file.createDataSet("va", va);
+    auto vb = vdset.read<std::vector<cv::Mat_<double>>>();
+    REQUIRE(vb.size() == va.size());
+    REQUIRE(vb[0](0, 0) == va[0](0, 0));
+}
+
+TEST_CASE("OpenCV subarrays") {
+    auto file = File("rw_opencv_subarray.h5", File::Truncate);
+
+    auto a = cv::Mat_<double>(3, 13);
+
+    SECTION("write") {
+        auto sa = cv::Mat_<double>(a.colRange(1, 4));
+        REQUIRE_THROWS(file.createDataSet("a", sa));
+    }
+
+    SECTION("read") {
+        auto b = cv::Mat_<double>(3, 17);
+        auto sb = cv::Mat_<double>(a.colRange(0, 13));
+        auto dset = file.createDataSet("a", a);
+
+        // Creates a new `Mat_` in `sb`.
+        dset.read(sb);
+    }
+}
+
+#endif
diff --git a/packages/HighFive/tests/unit/test_string.cpp b/packages/HighFive/tests/unit/test_string.cpp
new file mode 100644
index 000000000..ed6a4a5bf
--- /dev/null
+++ b/packages/HighFive/tests/unit/test_string.cpp
@@ -0,0 +1,351 @@
+/*
+ *  Copyright (c), 2017-2024, Blue Brain Project - EPFL
+ *
+ *  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)
+ *
+ */
+
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/catch_template_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_vector.hpp>
+
+#include <highfive/highfive.hpp>
+#include "tests_high_five.hpp"
+#include "create_traits.hpp"
+
+
+using namespace HighFive;
+using Catch::Matchers::Equals;
+
+TEST_CASE("StringType") {
+    SECTION("enshrine-defaults") {
+        auto fixed_length = FixedLengthStringType(32, StringPadding::SpacePadded);
+        auto variable_length = VariableLengthStringType();
+
+        REQUIRE(fixed_length.getCharacterSet() == CharacterSet::Ascii);
+        REQUIRE(variable_length.getCharacterSet() == CharacterSet::Ascii);
+    }
+
+    SECTION("fixed-length") {
+        auto fixed_length =
+            FixedLengthStringType(32, StringPadding::SpacePadded, CharacterSet::Utf8);
+        auto string_type = fixed_length.asStringType();
+
+        REQUIRE(string_type.getId() == fixed_length.getId());
+        REQUIRE(string_type.getCharacterSet() == CharacterSet::Utf8);
+        REQUIRE(string_type.getPadding() == StringPadding::SpacePadded);
+        REQUIRE(string_type.getSize() == 32);
+        REQUIRE(!string_type.isVariableStr());
+        REQUIRE(string_type.isFixedLenStr());
+    }
+
+    SECTION("variable-length") {
+        auto variable_length = VariableLengthStringType(CharacterSet::Utf8);
+        auto string_type = variable_length.asStringType();
+
+        REQUIRE(string_type.getId() == variable_length.getId());
+        REQUIRE(string_type.getCharacterSet() == CharacterSet::Utf8);
+        REQUIRE(string_type.isVariableStr());
+        REQUIRE(!string_type.isFixedLenStr());
+    }
+
+    SECTION("atomic") {
+        auto atomic = AtomicType<double>();
+        REQUIRE_THROWS(atomic.asStringType());
+    }
+}
+
+template <class CreateTraits>
+void check_single_string(File file, size_t string_length) {
+    auto value = std::string(string_length, 'o');
+    auto dataspace = DataSpace::From(value);
+
+    auto n_chars = value.size() + 1;
+    auto n_chars_overlength = n_chars + 10;
+    auto fixed_length = FixedLengthStringType(n_chars, StringPadding::NullTerminated);
+    auto overlength_nullterm = FixedLengthStringType(n_chars_overlength,
+                                                     StringPadding::NullTerminated);
+    auto overlength_nullpad = FixedLengthStringType(n_chars_overlength, StringPadding::NullPadded);
+    auto overlength_spacepad = FixedLengthStringType(n_chars_overlength,
+                                                     StringPadding::SpacePadded);
+    auto variable_length = VariableLengthStringType();
+
+    SECTION("automatic") {
+        auto obj = CreateTraits::create(file, "auto", value);
+        REQUIRE(obj.template read<std::string>() == value);
+    }
+
+    SECTION("fixed length") {
+        auto obj = CreateTraits::create(file, "fixed", dataspace, fixed_length);
+        obj.write(value);
+        REQUIRE(obj.template read<std::string>() == value);
+    }
+
+    SECTION("overlength null-terminated") {
+        auto obj =
+            CreateTraits::create(file, "overlength_nullterm", dataspace, overlength_nullterm);
+        obj.write(value);
+        REQUIRE(obj.template read<std::string>() == value);
+    }
+
+    SECTION("overlength null-padded") {
+        auto obj = CreateTraits::create(file, "overlength_nullpad", dataspace, overlength_nullpad);
+        obj.write(value);
+        auto expected = std::string(n_chars_overlength, '\0');
+        expected.replace(0, value.size(), value.data());
+        REQUIRE(obj.template read<std::string>() == expected);
+    }
+
+    SECTION("overlength space-padded") {
+        auto obj =
+            CreateTraits::create(file, "overlength_spacepad", dataspace, overlength_spacepad);
+        obj.write(value);
+        auto expected = std::string(n_chars_overlength, ' ');
+        expected.replace(0, value.size(), value.data());
+        REQUIRE(obj.template read<std::string>() == expected);
+    }
+
+    SECTION("variable length") {
+        auto obj = CreateTraits::create(file, "variable", dataspace, variable_length);
+        obj.write(value);
+        REQUIRE(obj.template read<std::string>() == value);
+    }
+}
+
+template <class CreateTraits>
+void check_multiple_string(File file, size_t string_length) {
+    using value_t = std::vector<std::string>;
+    auto value = value_t{std::string(string_length, 'o'), std::string(string_length, 'x')};
+
+    auto dataspace = DataSpace::From(value);
+
+    auto string_overlength = string_length + 10;
+    auto onpoint_nullpad = FixedLengthStringType(string_length, StringPadding::NullPadded);
+    auto onpoint_spacepad = FixedLengthStringType(string_length, StringPadding::SpacePadded);
+
+    auto overlength_nullterm = FixedLengthStringType(string_overlength,
+                                                     StringPadding::NullTerminated);
+    auto overlength_nullpad = FixedLengthStringType(string_overlength, StringPadding::NullPadded);
+    auto overlength_spacepad = FixedLengthStringType(string_overlength, StringPadding::SpacePadded);
+    auto variable_length = VariableLengthStringType();
+
+    auto check = [](const value_t actual, const value_t& expected) {
+        REQUIRE(actual.size() == expected.size());
+        for (size_t i = 0; i < actual.size(); ++i) {
+            REQUIRE(actual[i] == expected[i]);
+        }
+    };
+
+    SECTION("automatic") {
+        auto obj = CreateTraits::create(file, "auto", value);
+        check(obj.template read<value_t>(), value);
+    }
+
+    SECTION("variable length") {
+        auto obj = CreateTraits::create(file, "variable", dataspace, variable_length);
+        obj.write(value);
+        check(obj.template read<value_t>(), value);
+    }
+
+    auto make_padded_reference = [&](char pad, size_t n) {
+        auto expected = std::vector<std::string>(value.size(), std::string(n, pad));
+        for (size_t i = 0; i < value.size(); ++i) {
+            expected[i].replace(0, value[i].size(), value[i].data());
+        }
+
+        return expected;
+    };
+
+    auto check_fixed_length = [&](const std::string& label, size_t length) {
+        SECTION(label + " null-terminated") {
+            auto datatype = FixedLengthStringType(length + 1, StringPadding::NullTerminated);
+            auto obj = CreateTraits::create(file, label + "_nullterm", dataspace, datatype);
+            obj.write(value);
+            check(obj.template read<value_t>(), value);
+        }
+
+        SECTION(label + " null-padded") {
+            auto datatype = FixedLengthStringType(length, StringPadding::NullPadded);
+            auto obj = CreateTraits::create(file, label + "_nullpad", dataspace, datatype);
+            obj.write(value);
+            auto expected = make_padded_reference('\0', length);
+            check(obj.template read<value_t>(), expected);
+        }
+
+        SECTION(label + " space-padded") {
+            auto datatype = FixedLengthStringType(length, StringPadding::SpacePadded);
+            auto obj = CreateTraits::create(file, label + "_spacepad", dataspace, datatype);
+            obj.write(value);
+            auto expected = make_padded_reference(' ', length);
+            check(obj.template read<value_t>(), expected);
+        }
+    };
+
+    check_fixed_length("onpoint", string_length);
+    check_fixed_length("overlength", string_length + 5);
+
+
+    SECTION("underlength null-terminated") {
+        auto datatype = FixedLengthStringType(string_length, StringPadding::NullTerminated);
+        auto obj = CreateTraits::create(file, "underlength_nullterm", dataspace, datatype);
+        REQUIRE_THROWS(obj.write(value));
+    }
+
+    SECTION("underlength nullpad") {
+        auto datatype = FixedLengthStringType(string_length - 1, StringPadding::NullPadded);
+        auto obj = CreateTraits::create(file, "underlength_nullpad", dataspace, datatype);
+        REQUIRE_THROWS(obj.write(value));
+    }
+
+    SECTION("underlength spacepad") {
+        auto datatype = FixedLengthStringType(string_length - 1, StringPadding::NullTerminated);
+        auto obj = CreateTraits::create(file, "underlength_spacepad", dataspace, datatype);
+        REQUIRE_THROWS(obj.write(value));
+    }
+}
+
+TEST_CASE("HighFiveSTDString (dataset, single, short)") {
+    File file("std_string_dataset_single_short.h5", File::Truncate);
+    check_single_string<testing::DataSetCreateTraits>(file, 3);
+}
+
+TEST_CASE("HighFiveSTDString (attribute, single, short)") {
+    File file("std_string_attribute_single_short.h5", File::Truncate);
+    check_single_string<testing::AttributeCreateTraits>(file, 3);
+}
+
+TEST_CASE("HighFiveSTDString (dataset, single, long)") {
+    File file("std_string_dataset_single_long.h5", File::Truncate);
+    check_single_string<testing::DataSetCreateTraits>(file, 256);
+}
+
+TEST_CASE("HighFiveSTDString (attribute, single, long)") {
+    File file("std_string_attribute_single_long.h5", File::Truncate);
+    check_single_string<testing::AttributeCreateTraits>(file, 256);
+}
+
+TEST_CASE("HighFiveSTDString (dataset, multiple, short)") {
+    File file("std_string_dataset_multiple_short.h5", File::Truncate);
+    check_multiple_string<testing::DataSetCreateTraits>(file, 3);
+}
+
+TEST_CASE("HighFiveSTDString (attribute, multiple, short)") {
+    File file("std_string_attribute_multiple_short.h5", File::Truncate);
+    check_multiple_string<testing::AttributeCreateTraits>(file, 3);
+}
+
+TEST_CASE("HighFiveSTDString (dataset, multiple, long)") {
+    File file("std_string_dataset_multiple_long.h5", File::Truncate);
+    check_multiple_string<testing::DataSetCreateTraits>(file, 256);
+}
+
+TEST_CASE("HighFiveSTDString (attribute, multiple, long)") {
+    File file("std_string_attribute_multiple_long.h5", File::Truncate);
+    check_multiple_string<testing::AttributeCreateTraits>(file, 256);
+}
+
+TEST_CASE("HighFiveFixedString") {
+    const std::string file_name("array_atomic_types.h5");
+    const std::string group_1("group1");
+
+    // Create a new file using the default property lists.
+    File file(file_name, File::ReadWrite | File::Create | File::Truncate);
+    char raw_strings[][10] = {"abcd", "1234"};
+
+    /// This will not compile - only char arrays - hits static_assert with a nice
+    /// error
+    // file.createDataSet<int[10]>(ds_name, DataSpace(2)));
+
+    {  // But char should be fine
+        auto ds = file.createDataSet<char[10]>("ds1", DataSpace(2));
+        CHECK(ds.getDataType().getClass() == DataTypeClass::String);
+        ds.write(raw_strings);
+    }
+
+    {  // char[] is, by default, int8
+        auto ds2 = file.createDataSet("ds2", raw_strings);
+        CHECK(ds2.getDataType().getClass() == DataTypeClass::Integer);
+    }
+
+    {  // String Truncate happens low-level if well setup
+        auto ds3 = file.createDataSet<char[6]>("ds3", DataSpace::FromCharArrayStrings(raw_strings));
+        ds3.write(raw_strings);
+    }
+
+    {  // Write as raw elements from pointer (with const)
+        const char(*strings_fixed)[10] = raw_strings;
+        // With a pointer we dont know how many strings -> manual DataSpace
+        file.createDataSet<char[10]>("ds4", DataSpace(2)).write(strings_fixed);
+    }
+
+
+    {  // Cant convert flex-length to fixed-length
+        const char* buffer[] = {"abcd", "1234"};
+        SilenceHDF5 silencer;
+        CHECK_THROWS_AS(file.createDataSet<char[10]>("ds5", DataSpace(2)).write(buffer),
+                        HighFive::DataSetException);
+    }
+
+    {  // scalar char strings
+        const char buffer[] = "abcd";
+        file.createDataSet<char[10]>("ds6", DataSpace(1)).write(buffer);
+    }
+
+    {
+        // Direct way of writing `std::string` as a fixed length
+        // HDF5 string.
+
+        std::string value = "foo";
+        auto n_chars = value.size() + 1;
+
+        auto datatype = FixedLengthStringType(n_chars, StringPadding::NullTerminated);
+        auto dataspace = DataSpace(1);
+
+        auto ds = file.createDataSet("ds8", dataspace, datatype);
+        ds.write_raw(value.data(), datatype);
+
+        {
+            // Due to missing non-const overload of `data()` until C++17 we'll
+            // read into something else instead (don't forget the '\0').
+            auto expected = std::vector<char>(n_chars, '!');
+            ds.read_raw(expected.data(), datatype);
+
+            CHECK(expected.size() == value.size() + 1);
+            for (size_t i = 0; i < value.size(); ++i) {
+                REQUIRE(expected[i] == value[i]);
+            }
+        }
+
+#if HIGHFIVE_CXX_STD >= 17
+        {
+            auto expected = std::string(value.size(), '-');
+            ds.read_raw(expected.data(), datatype);
+
+            REQUIRE(expected == value);
+        }
+#endif
+    }
+
+    {
+        size_t n_chars = 4;
+        size_t n_strings = 2;
+
+        std::vector<char> value(n_chars * n_strings, '!');
+
+        auto datatype = FixedLengthStringType(n_chars, StringPadding::NullTerminated);
+        auto dataspace = DataSpace(n_strings);
+
+        auto ds = file.createDataSet("ds9", dataspace, datatype);
+        ds.write_raw(value.data(), datatype);
+
+        auto expected = std::vector<char>(value.size(), '-');
+        ds.read_raw(expected.data(), datatype);
+
+        CHECK(expected.size() == value.size());
+        for (size_t i = 0; i < value.size(); ++i) {
+            REQUIRE(expected[i] == value[i]);
+        }
+    }
+}
diff --git a/packages/HighFive/tests/unit/test_xtensor.cpp b/packages/HighFive/tests/unit/test_xtensor.cpp
new file mode 100644
index 000000000..ac0b4d743
--- /dev/null
+++ b/packages/HighFive/tests/unit/test_xtensor.cpp
@@ -0,0 +1,142 @@
+/*
+ *  Copyright (c), 2024, Blue Brain Project - EPFL
+ *
+ *  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)
+ *
+ */
+#if HIGHFIVE_TEST_XTENSOR
+#include <string>
+#include <sstream>
+
+#include <catch2/catch_template_test_macros.hpp>
+
+#include <highfive/highfive.hpp>
+#include <xtensor/xtensor.hpp>
+#include <xtensor/xview.hpp>
+#include <xtensor/xio.hpp>
+#include <highfive/xtensor.hpp>
+
+#include "data_generator.hpp"
+
+using namespace HighFive;
+
+template <size_t N>
+std::array<size_t, N> asStaticShape(const std::vector<size_t>& dims) {
+    assert(dims.size() == N);
+
+    std::array<size_t, N> shape;
+    std::copy(dims.cbegin(), dims.cend(), shape.begin());
+
+    return shape;
+}
+
+TEST_CASE("xt::xarray reshape", "[xtensor]") {
+    const std::string file_name("rw_dataset_xarray.h5");
+
+    File file(file_name, File::Truncate);
+
+    std::vector<size_t> shape{3, 2, 4};
+    std::vector<size_t> compatible_shape{1, 3, 2, 4};
+    std::vector<size_t> incompatible_shape{5, 2, 4};
+
+    xt::xarray<double> a = testing::DataGenerator<xt::xtensor<double, 3>>::create(shape);
+    xt::xarray<double> b(compatible_shape);
+    xt::xarray<double> c(incompatible_shape);
+
+    auto dset = file.createDataSet("baz", a);
+
+    SECTION("xarray_adaptor") {
+        // Changes the shape.
+        auto b_adapt = xt::adapt(b.data(), b.size(), xt::no_ownership(), b.shape());
+        dset.read(b_adapt);
+        REQUIRE(b_adapt.shape() == shape);
+
+        // But can't change the number of elements.
+        auto c_adapt = xt::adapt(c.data(), c.size(), xt::no_ownership(), c.shape());
+        REQUIRE_THROWS(dset.read(c_adapt));
+    }
+
+    SECTION("xtensor_adaptor") {
+        auto b_shape = asStaticShape<4>(compatible_shape);
+        auto c_shape = asStaticShape<3>(incompatible_shape);
+
+        // Doesn't change the shape:
+        auto b_adapt = xt::adapt(b.data(), b.size(), xt::no_ownership(), b_shape);
+        REQUIRE_THROWS(dset.read(b_adapt));
+
+        // and can't change the number of elements:
+        auto c_adapt = xt::adapt(c.data(), c.size(), xt::no_ownership(), c_shape);
+        REQUIRE_THROWS(dset.read(c_adapt));
+    }
+}
+
+TEST_CASE("xt::xview example", "[xtensor]") {
+    File file("rw_dataset_xview.h5", File::Truncate);
+
+    std::vector<size_t> shape{13, 5, 7};
+    xt::xarray<double> a = testing::DataGenerator<xt::xtensor<double, 3>>::create(shape);
+    auto c = xt::view(a, xt::range(3, 31, 4), xt::all(), xt::drop(0, 3, 4, 5));
+
+    auto dset = file.createDataSet("c", c);
+    auto d = dset.read<xt::xarray<double>>();
+    auto e = dset.read<xt::xarray<double, xt::layout_type::column_major>>();
+
+    REQUIRE(d == c);
+    REQUIRE(e == c);
+}
+
+template <class XTensor>
+void check_xtensor_scalar(File& file) {
+    XTensor a;
+    a = 42.0;
+    REQUIRE(a.shape() == std::vector<size_t>{});
+
+    SECTION("read") {
+        auto dset = file.createDataSet("a", a);
+        REQUIRE(dset.template read<double>() == a(0));
+    }
+
+    SECTION("write") {
+        double b = -42.0;
+        auto dset = file.createDataSet("b", b);
+        REQUIRE(dset.template read<xt::xarray<double>>()(0) == b);
+    }
+}
+
+TEST_CASE("xt::xarray scalar", "[xtensor]") {
+    File file("rw_dataset_xarray_scalar.h5", File::Truncate);
+    check_xtensor_scalar<xt::xarray<double>>(file);
+}
+
+TEST_CASE("xt::xtensor scalar", "[xtensor]") {
+    File file("rw_dataset_xtensor_scalar.h5", File::Truncate);
+    check_xtensor_scalar<xt::xarray<double>>(file);
+}
+
+template <class XTensor>
+void check_xtensor_empty(File& file, const XTensor& a, const std::vector<size_t>& expected_dims) {
+    auto dset = file.createDataSet("a", a);
+    auto b = dset.template read<XTensor>();
+    REQUIRE(b.size() == 0);
+    REQUIRE(b == a);
+
+    auto c = std::vector<XTensor>{};
+    auto c_shape = details::inspector<decltype(c)>::getDimensions(c);
+    REQUIRE(c_shape == expected_dims);
+}
+
+TEST_CASE("xt::xtensor empty", "[xtensor]") {
+    File file("rw_dataset_xtensor_empty.h5", File::Truncate);
+    xt::xtensor<double, 3> a({0, 1, 1});
+    check_xtensor_empty(file, a, {0, 1, 1, 1});
+}
+
+TEST_CASE("xt::xarray empty", "[xtensor]") {
+    File file("rw_dataset_xarray_empty.h5", File::Truncate);
+    xt::xarray<double> a(std::vector<size_t>{1, 0, 1});
+    check_xtensor_empty(file, a, {0});
+}
+
+#endif
diff --git a/packages/HighFive/tests/unit/tests_high_five.hpp b/packages/HighFive/tests/unit/tests_high_five.hpp
index 25839c69e..d9a4ed34d 100644
--- a/packages/HighFive/tests/unit/tests_high_five.hpp
+++ b/packages/HighFive/tests/unit/tests_high_five.hpp
@@ -21,6 +21,7 @@
 // The list of identifiers is taken from `Boost::Predef`.
 #if defined(_WIN32) || defined(_WIN64) || defined(__WIN32__) || defined(__TOS_WIN__) || \
     defined(__WINDOWS__)
+#define NOMINMAX
 #include <Windows.h>
 #endif
 
diff --git a/packages/HighFive/tests/unit/tests_high_five_base.cpp b/packages/HighFive/tests/unit/tests_high_five_base.cpp
index bc33d5dad..f52c2dcb8 100644
--- a/packages/HighFive/tests/unit/tests_high_five_base.cpp
+++ b/packages/HighFive/tests/unit/tests_high_five_base.cpp
@@ -27,6 +27,7 @@
 
 #include <highfive/highfive.hpp>
 #include "tests_high_five.hpp"
+#include "create_traits.hpp"
 
 #ifdef HIGHFIVE_TEST_BOOST
 #include <highfive/boost.hpp>
@@ -36,6 +37,9 @@
 #include <highfive/eigen.hpp>
 #endif
 
+#ifdef HIGHFIVE_TEST_SPAN
+#include <highfive/span.hpp>
+#endif
 
 using namespace HighFive;
 using Catch::Matchers::Equals;
@@ -815,45 +819,6 @@ TEST_CASE("Test simple listings") {
     }
 }
 
-TEST_CASE("StringType") {
-    SECTION("enshrine-defaults") {
-        auto fixed_length = FixedLengthStringType(32, StringPadding::SpacePadded);
-        auto variable_length = VariableLengthStringType();
-
-        REQUIRE(fixed_length.getCharacterSet() == CharacterSet::Ascii);
-        REQUIRE(variable_length.getCharacterSet() == CharacterSet::Ascii);
-    }
-
-    SECTION("fixed-length") {
-        auto fixed_length =
-            FixedLengthStringType(32, StringPadding::SpacePadded, CharacterSet::Utf8);
-        auto string_type = fixed_length.asStringType();
-
-        REQUIRE(string_type.getId() == fixed_length.getId());
-        REQUIRE(string_type.getCharacterSet() == CharacterSet::Utf8);
-        REQUIRE(string_type.getPadding() == StringPadding::SpacePadded);
-        REQUIRE(string_type.getSize() == 32);
-        REQUIRE(!string_type.isVariableStr());
-        REQUIRE(string_type.isFixedLenStr());
-    }
-
-    SECTION("variable-length") {
-        auto variable_length = VariableLengthStringType(CharacterSet::Utf8);
-        auto string_type = variable_length.asStringType();
-
-        REQUIRE(string_type.getId() == variable_length.getId());
-        REQUIRE(string_type.getCharacterSet() == CharacterSet::Utf8);
-        REQUIRE(string_type.isVariableStr());
-        REQUIRE(!string_type.isFixedLenStr());
-    }
-
-    SECTION("atomic") {
-        auto atomic = AtomicType<double>();
-        REQUIRE_THROWS(atomic.asStringType());
-    }
-}
-
-
 TEST_CASE("DataTypeEqualTakeBack") {
     const std::string file_name("h5tutr_dset.h5");
     const std::string dataset_name("dset");
@@ -1462,413 +1427,161 @@ TEMPLATE_LIST_TEST_CASE("ReadWriteSzip", "[template]", dataset_test_types) {
     }
 }
 
-TEST_CASE("CheckDimensions") {
-    // List of dims which can all be one-dimensional.
-    std::vector<std::vector<size_t>> test_cases{
-        {1ul, 3ul}, {3ul, 1ul}, {1ul, 1ul, 3ul}, {3ul, 1ul, 1ul}, {1ul, 3ul, 1ul}};
-
-    for (const auto& dims: test_cases) {
-        auto actual = details::checkDimensions(dims, 1ul);
+template <class CreateTraits>
+void check_broadcast_scalar_memspace(File& file,
+                                     const std::string& name,
+                                     const std::vector<size_t>& dims) {
+    auto datatype = create_datatype<double>();
+    auto obj = CreateTraits::create(file, name, DataSpace(dims), datatype);
 
-        INFO("dims = " + details::format_vector(dims) + ", n_dims = 1");
-        CHECK(actual);
-
-        INFO("dims = " + details::format_vector(dims) + ", n_dims = 1");
-        CHECK(!details::checkDimensions(dims, dims.size() + 1));
-    }
+    double expected = 3.0;
+    obj.write(expected);
 
-    CHECK(details::checkDimensions(std::vector<size_t>{1ul}, 0ul));
-    CHECK(details::checkDimensions(std::vector<size_t>{1ul}, 1ul));
-
-    CHECK(!details::checkDimensions(std::vector<size_t>{0ul}, 0ul));
-    CHECK(!details::checkDimensions(std::vector<size_t>{2ul}, 0ul));
-
-    CHECK(!details::checkDimensions(std::vector<size_t>{1ul, 2ul, 3ul}, 2ul));
-    CHECK(details::checkDimensions(std::vector<size_t>{3ul, 2ul, 1ul}, 2ul));
-
-    CHECK(details::checkDimensions(std::vector<size_t>{1ul, 1ul, 1ul, 1ul}, 1ul));
-
-    CHECK(details::checkDimensions(std::vector<size_t>{}, 0ul));
-    CHECK(!details::checkDimensions(std::vector<size_t>{}, 1ul));
-    CHECK(!details::checkDimensions(std::vector<size_t>{}, 2ul));
+    auto actual = obj.template read<double>();
+    CHECK(actual == expected);
 }
 
+TEST_CASE("Broadcast scalar memspace, dset") {
+    File file("h5_broadcast_scalar_memspace_dset.h5", File::Truncate);
 
-TEST_CASE("SqueezeDimensions") {
-    SECTION("possible") {
-        // List of testcases: the first number is n_dims then the input dimensions
-        // and finally the squeezed dimensions.
-        std::vector<std::tuple<size_t, std::vector<size_t>, std::vector<size_t>>> test_cases{
-            {1ul, {3ul, 1ul}, {3ul}},
-
-            {1ul, {1ul, 1ul, 1ul}, {1ul}},
-
-            {1ul, {1ul, 3ul, 1ul}, {3ul}},
-
-            {1ul, {3ul, 1ul, 1ul}, {3ul}},
-            {2ul, {3ul, 1ul, 1ul}, {3ul, 1ul}},
-            {3ul, {3ul, 1ul, 1ul}, {3ul, 1ul, 1ul}},
-
-            {3ul, {2ul, 1ul, 3ul}, {2ul, 1ul, 3ul}}};
-
-        for (const auto& tc: test_cases) {
-            auto n_dim_requested = std::get<0>(tc);
-            auto dims = std::get<1>(tc);
-            auto expected = std::get<2>(tc);
-            auto actual = details::squeezeDimensions(dims, n_dim_requested);
-
-            CHECK(actual == expected);
-        }
+    SECTION("[1]") {
+        check_broadcast_scalar_memspace<testing::DataSetCreateTraits>(file, "dset", {1});
     }
 
-    SECTION("impossible") {
-        // List of testcases: the first number is n_dims then the input dimensions
-        // and finally the squeezed dimensions.
-        std::vector<std::tuple<size_t, std::vector<size_t>>> test_cases{{1ul, {1ul, 2ul, 3ul}},
-                                                                        {2ul, {1ul, 2ul, 3ul, 1ul}},
-
-                                                                        {1ul, {2ul, 1ul, 3ul}},
-                                                                        {2ul, {2ul, 1ul, 3ul}}};
-
-        for (const auto& tc: test_cases) {
-            auto n_dim_requested = std::get<0>(tc);
-            auto dims = std::get<1>(tc);
-
-            CHECK_THROWS(details::squeezeDimensions(dims, n_dim_requested));
-        }
+    SECTION("[1, 1, 1]") {
+        check_broadcast_scalar_memspace<testing::DataSetCreateTraits>(file, "dset", {1, 1, 1});
     }
 }
 
-void check_broadcast_1d(HighFive::File& file,
-                        const std::vector<size_t> dims,
-                        const std::string& dataset_name) {
-    // This checks that:
-    //   - we can write 1D array into 2D dataset.
-    //   - we can read 2D dataset into a 1D array.
-    std::vector<double> input_data{5.0, 6.0, 7.0};
-
+TEST_CASE("Broadcast scalar memspace, attr") {
+    File file("h5_broadcast_scalar_memspace_attr.h5", File::Truncate);
 
-    DataSpace dataspace(dims);
-    DataSet dataset = file.createDataSet(dataset_name, dataspace, AtomicType<double>());
-
-    dataset.write(input_data);
-
-    {
-        std::vector<double> read_back;
-        dataset.read(read_back);
-
-        CHECK(read_back == input_data);
+    SECTION("[1]") {
+        check_broadcast_scalar_memspace<testing::AttributeCreateTraits>(file, "attr", {1});
     }
 
-    {
-        auto read_back = dataset.read<std::vector<double>>();
-        CHECK(read_back == input_data);
+    SECTION("[1, 1, 1]") {
+        check_broadcast_scalar_memspace<testing::AttributeCreateTraits>(file, "attr", {1, 1, 1});
     }
 }
 
-// Broadcasting is supported
-TEST_CASE("ReadInBroadcastDims") {
-    const std::string file_name("h5_broadcast_dset.h5");
-    const std::string dataset_name("dset");
+template <class CreateTraits>
+void check_broadcast_scalar_filespace(File& file, const std::string& name) {
+    auto datatype = create_datatype<double>();
+    auto obj = CreateTraits::create(file, name, DataSpace::Scalar(), datatype);
 
-    // Create a new file using the default property lists.
-    File file(file_name, File::Truncate);
+    auto value = std::vector<double>{3.0};
 
-    SECTION("one-dimensional (1, 3)") {
-        check_broadcast_1d(file, {1, 3}, dataset_name + "_a");
-    }
-
-    SECTION("one-dimensional (3, 1)") {
-        check_broadcast_1d(file, {3, 1}, dataset_name + "_b");
-    }
-
-    SECTION("two-dimensional (2, 3, 1)") {
-        std::vector<size_t> dims{2, 3, 1};
-        std::vector<std::vector<double>> input_data_2d{{2.0, 3.0, 4.0}, {10.0, 11.0, 12.0}};
+    REQUIRE_THROWS(obj.write(value));
+    REQUIRE_THROWS(obj.template read<std::vector<double>>());
+    REQUIRE_THROWS(obj.read(value));
+}
 
-        DataSpace dataspace(dims);
-        DataSet dataset = file.createDataSet(dataset_name + "_c", dataspace, AtomicType<double>());
+TEST_CASE("Broadcast scalar filespace, dset") {
+    File file("h5_broadcast_scalar_filespace_dset.h5", File::Truncate);
+    check_broadcast_scalar_filespace<testing::DataSetCreateTraits>(file, "dset");
+}
 
-        dataset.write(input_data_2d);
+TEST_CASE("Broadcast scalar filespace, attr") {
+    File file("h5_broadcast_scalar_filespace_attr.h5", File::Truncate);
+    check_broadcast_scalar_filespace<testing::AttributeCreateTraits>(file, "attr");
+}
 
-        auto check = [](const std::vector<std::vector<double>>& lhs,
-                        const std::vector<std::vector<double>>& rhs) {
-            CHECK(lhs.size() == rhs.size());
-            for (size_t i = 0; i < rhs.size(); ++i) {
-                CHECK(lhs[i].size() == rhs[i].size());
+TEST_CASE("squeeze") {
+    CHECK(detail::squeeze({}, {}) == std::vector<size_t>{});
+    CHECK(detail::squeeze({3, 1, 1}, {}) == std::vector<size_t>{3, 1, 1});
+    CHECK(detail::squeeze({3, 1, 1}, {2, 1}) == std::vector<size_t>{3});
+    CHECK(detail::squeeze({1, 3, 1, 2}, {2, 0}) == std::vector<size_t>{3, 2});
 
-                for (size_t j = 0; j < rhs[i].size(); ++j) {
-                    CHECK(lhs[i][j] == rhs[i][j]);
-                }
-            }
-        };
+    CHECK_THROWS(detail::squeeze({3, 1, 1}, {3}));
+    CHECK_THROWS(detail::squeeze({3, 1, 1}, {0}));
+    CHECK_THROWS(detail::squeeze({}, {0}));
+}
 
-        {
-            std::vector<std::vector<double>> read_back;
-            dataset.read(read_back);
+template <class CreateTraits>
+void check_modify_memspace(File& file, const std::string& name) {
+    auto expected_values = std::vector<double>{1.0, 2.0, 3.0};
+    auto values = std::vector<std::vector<double>>{expected_values};
 
-            check(read_back, input_data_2d);
-        }
+    auto obj = CreateTraits::create(file, name, values);
+    SECTION("squeeze") {
+        auto actual_values = obj.squeezeMemSpace({0}).template read<std::vector<double>>();
 
-        {
-            auto read_back = dataset.read<std::vector<std::vector<double>>>();
-            check(read_back, input_data_2d);
+        REQUIRE(actual_values.size() == expected_values.size());
+        for (size_t i = 0; i < actual_values.size(); ++i) {
+            REQUIRE(actual_values[i] == expected_values[i]);
         }
     }
 
-    SECTION("one-dimensional fixed length string") {
-        std::vector<size_t> dims{1, 1, 2};
-        char input_data[2] = "a";
-
-        DataSpace dataspace(dims);
-        DataSet dataset = file.createDataSet(dataset_name + "_d", dataspace, AtomicType<char>());
-        dataset.write(input_data);
-
-        {
-            char read_back[2];
-            dataset.read(read_back);
+    SECTION("reshape") {
+        auto actual_values = obj.reshapeMemSpace({3}).template read<std::vector<double>>();
 
-            CHECK(read_back[0] == 'a');
-            CHECK(read_back[1] == '\0');
+        REQUIRE(actual_values.size() == expected_values.size());
+        for (size_t i = 0; i < actual_values.size(); ++i) {
+            REQUIRE(actual_values[i] == expected_values[i]);
         }
     }
 }
 
-
-template <int n_dim>
-struct CreateEmptyVector;
-
-template <>
-struct CreateEmptyVector<1> {
-    using container_type = std::vector<int>;
-
-    static container_type create(const std::vector<size_t>& dims) {
-        return container_type(dims[0], 2);
-    }
-};
-
-template <int n_dim>
-struct CreateEmptyVector {
-    using container_type = std::vector<typename CreateEmptyVector<n_dim - 1>::container_type>;
-
-    static container_type create(const std::vector<size_t>& dims) {
-        auto subdims = std::vector<size_t>(dims.begin() + 1, dims.end());
-        return container_type(dims[0], CreateEmptyVector<n_dim - 1>::create(subdims));
-    }
-};
-
-#ifdef HIGHFIVE_TEST_BOOST
-template <int n_dim>
-struct CreateEmptyBoostMultiArray {
-    using container_type = boost::multi_array<int, static_cast<long unsigned>(n_dim)>;
-
-    static container_type create(const std::vector<size_t>& dims) {
-        auto container = container_type(dims);
-
-        auto raw_data = std::vector<int>(compute_total_size(dims));
-        container.assign(raw_data.begin(), raw_data.end());
-
-        return container;
-    }
-};
-#endif
-
-
-#ifdef HIGHFIVE_TEST_EIGEN
-struct CreateEmptyEigenVector {
-    using container_type = Eigen::VectorXi;
-
-    static container_type create(const std::vector<size_t>& dims) {
-        return container_type::Constant(int(dims[0]), 2);
-    }
-};
-
-struct CreateEmptyEigenMatrix {
-    using container_type = Eigen::MatrixXi;
-
-    static container_type create(const std::vector<size_t>& dims) {
-        return container_type::Constant(int(dims[0]), int(dims[1]), 2);
-    }
-};
-#endif
-
-template <class Container>
-void check_empty_dimensions(const Container& container, const std::vector<size_t>& expected_dims) {
-    auto deduced_dims = details::inspector<Container>::getDimensions(container);
-
-    REQUIRE(expected_dims.size() == deduced_dims.size());
-
-    // The dims after hitting the first `0` are finicky. We allow those to be deduced as either `1`
-    // or what the original dims said. The `1` allows broadcasting, the "same as original" enables
-    // statically sized objects, which conceptually have dims, even if there's no object.
-    bool allow_one = false;
-    for (size_t i = 0; i < expected_dims.size(); ++i) {
-        REQUIRE(((expected_dims[i] == deduced_dims[i]) || (allow_one && (deduced_dims[i] == 1ul))));
-
-        if (expected_dims[i] == 0) {
-            allow_one = true;
-        }
-    }
+TEST_CASE("Modify MemSpace, dset") {
+    File file("h5_modify_memspace_dset.h5", File::Truncate);
+    check_modify_memspace<testing::DataSetCreateTraits>(file, "dset");
 }
 
-template <class CreateContainer>
-void check_empty_dimensions(const std::vector<size_t>& dims) {
-    auto input_data = CreateContainer::create(dims);
-    check_empty_dimensions(input_data, dims);
+TEST_CASE("Modify MemSpace, attr") {
+    File file("h5_modify_memspace_attr.h5", File::Truncate);
+    check_modify_memspace<testing::AttributeCreateTraits>(file, "attr");
 }
 
-struct ReadWriteAttribute {
-    template <class Container>
-    static void create(HighFive::File& file, const std::string& name, const Container& container) {
-        file.createAttribute(name, container);
-    }
-
-    static HighFive::Attribute get(HighFive::File& file, const std::string& name) {
-        return file.getAttribute(name);
-    }
-};
+template <class CreateTraits>
+void check_modify_scalar_filespace(File& file, const std::string& name) {
+    auto expected_value = 3.0;
 
-struct ReadWriteDataSet {
-    template <class Container>
-    static void create(HighFive::File& file, const std::string& name, const Container& container) {
-        file.createDataSet(name, container);
-    }
+    auto obj = CreateTraits::create(file, name, expected_value);
+    SECTION("reshape") {
+        auto actual_values = obj.reshapeMemSpace({1}).template read<std::vector<double>>();
 
-    static HighFive::DataSet get(HighFive::File& file, const std::string& name) {
-        return file.getDataSet(name);
-    }
-};
-
-template <class ReadWriteInterface, class CreateContainer>
-void check_empty_read_write_cycle(const std::vector<size_t>& dims) {
-    using container_type = typename CreateContainer::container_type;
-
-    const std::string file_name("h5_empty_attr.h5");
-    const std::string dataset_name("dset");
-    File file(file_name, File::Truncate);
-
-    auto input_data = CreateContainer::create(dims);
-    ReadWriteInterface::create(file, dataset_name, input_data);
-
-    SECTION("read; one-dimensional vector (empty)") {
-        auto output_data = CreateEmptyVector<1>::create({0ul});
-
-        ReadWriteInterface::get(file, dataset_name).read(output_data);
-        check_empty_dimensions(output_data, {0ul});
-    }
-
-    SECTION("read; pre-allocated (empty)") {
-        auto output_data = CreateContainer::create(dims);
-        ReadWriteInterface::get(file, dataset_name).read(output_data);
-
-        check_empty_dimensions(output_data, dims);
-    }
-
-    SECTION("read; pre-allocated (oversized)") {
-        auto oversize_dims = std::vector<size_t>(dims.size(), 2ul);
-        auto output_data = CreateContainer::create(oversize_dims);
-        ReadWriteInterface::get(file, dataset_name).read(output_data);
-
-        check_empty_dimensions(output_data, dims);
-    }
-
-    SECTION("read; auto-allocated") {
-        auto output_data =
-            ReadWriteInterface::get(file, dataset_name).template read<container_type>();
-        check_empty_dimensions(output_data, dims);
+        REQUIRE(actual_values.size() == 1);
+        REQUIRE(actual_values[0] == expected_value);
     }
 }
 
-template <class CreateContainer>
-void check_empty_dataset(const std::vector<size_t>& dims) {
-    check_empty_read_write_cycle<ReadWriteDataSet, CreateContainer>(dims);
+TEST_CASE("Modify Scalar FileSpace, dset") {
+    File file("h5_modify_scalar_filespace_dset.h5", File::Truncate);
+    check_modify_scalar_filespace<testing::DataSetCreateTraits>(file, "dset");
 }
 
-template <class CreateContainer>
-void check_empty_attribute(const std::vector<size_t>& dims) {
-    check_empty_read_write_cycle<ReadWriteAttribute, CreateContainer>(dims);
+TEST_CASE("Modify Scalar FileSpace, attr") {
+    File file("h5_modify_scalar_filespace_attr.h5", File::Truncate);
+    check_modify_scalar_filespace<testing::AttributeCreateTraits>(file, "attr");
 }
 
-template <class CreateContainer>
-void check_empty_everything(const std::vector<size_t>& dims) {
-    SECTION("Empty dimensions") {
-        check_empty_dimensions<CreateContainer>(dims);
-    }
-
-    SECTION("Empty datasets") {
-        check_empty_dataset<CreateContainer>(dims);
-    }
+template <class CreateTraits>
+void check_modify_scalar_memspace(File& file, const std::string& name) {
+    auto expected_value = std::vector<double>{3.0};
 
-    SECTION("Empty attribute") {
-        check_empty_attribute<CreateContainer>(dims);
+    auto obj = CreateTraits::create(file, name, expected_value);
+    SECTION("squeeze") {
+        auto actual_value = obj.squeezeMemSpace({0}).template read<double>();
+        REQUIRE(actual_value == expected_value[0]);
     }
-}
-
-#ifdef HIGHFIVE_TEST_EIGEN
-template <int ndim>
-void check_empty_eigen(const std::vector<size_t>&) {}
 
-template <>
-void check_empty_eigen<1>(const std::vector<size_t>& dims) {
-    SECTION("Eigen::Vector") {
-        check_empty_everything<CreateEmptyEigenVector>({dims[0], 1ul});
+    SECTION("reshape") {
+        auto actual_value = obj.reshapeMemSpace({}).template read<double>();
+        REQUIRE(actual_value == expected_value[0]);
     }
 }
 
-template <>
-void check_empty_eigen<2>(const std::vector<size_t>& dims) {
-    SECTION("Eigen::Matrix") {
-        check_empty_everything<CreateEmptyEigenMatrix>(dims);
-    }
+TEST_CASE("Modify Scalar MemSpace, dset") {
+    File file("h5_modify_scalar_memspace_dset.h5", File::Truncate);
+    check_modify_scalar_memspace<testing::DataSetCreateTraits>(file, "dset");
 }
-#endif
-
-template <int ndim>
-void check_empty(const std::vector<size_t>& dims) {
-    REQUIRE(dims.size() == ndim);
-
-    SECTION("std::vector") {
-        check_empty_everything<CreateEmptyVector<ndim>>(dims);
-    }
-
-#ifdef HIGHFIVE_TEST_BOOST
-    SECTION("boost::multi_array") {
-        check_empty_everything<CreateEmptyBoostMultiArray<ndim>>(dims);
-    }
-#endif
 
-#ifdef HIGHFIVE_TEST_EIGEN
-    check_empty_eigen<ndim>(dims);
-#endif
+TEST_CASE("Modify Scalar MemSpace, attr") {
+    File file("h5_modify_scalar_memspace_attr.h5", File::Truncate);
+    check_modify_scalar_memspace<testing::AttributeCreateTraits>(file, "attr");
 }
 
-TEST_CASE("Empty arrays") {
-    SECTION("one-dimensional") {
-        check_empty<1>({0ul});
-    }
-
-    SECTION("two-dimensional") {
-        std::vector<std::vector<size_t>> testcases{{0ul, 1ul}, {1ul, 0ul}};
-
-        for (const auto& dims: testcases) {
-            SECTION(details::format_vector(dims)) {
-                check_empty<2>(dims);
-            }
-        }
-    }
-
-    SECTION("three-dimensional") {
-        std::vector<std::vector<size_t>> testcases{{0ul, 1ul, 1ul},
-                                                   {1ul, 1ul, 0ul},
-                                                   {1ul, 0ul, 1ul}};
-
-        for (const auto& dims: testcases) {
-            SECTION(details::format_vector(dims)) {
-                check_empty<3>(dims);
-            }
-        }
-    }
-}
 
 TEST_CASE("HighFiveRecursiveGroups") {
     const std::string file_name("h5_ds_exist.h5");
@@ -2284,333 +1997,6 @@ TEST_CASE("DirectWriteBool") {
 }
 
 
-class ForwardToAttribute {
-  public:
-    ForwardToAttribute(const HighFive::File& file)
-        : _file(file) {}
-
-    template <class T>
-    HighFive::Attribute create(const std::string& name, const T& value) {
-        return _file.createAttribute(name, value);
-    }
-
-    HighFive::Attribute create(const std::string& name,
-                               const HighFive::DataSpace filespace,
-                               const HighFive::DataType& datatype) {
-        return _file.createAttribute(name, filespace, datatype);
-    }
-
-    HighFive::Attribute get(const std::string& name) {
-        return _file.getAttribute(name);
-    }
-
-  private:
-    HighFive::File _file;
-};
-
-class ForwardToDataSet {
-  public:
-    ForwardToDataSet(const HighFive::File& file)
-        : _file(file) {}
-
-    template <class T>
-    HighFive::DataSet create(const std::string& name, const T& value) {
-        return _file.createDataSet(name, value);
-    }
-
-    HighFive::DataSet create(const std::string& name,
-                             const HighFive::DataSpace filespace,
-                             const HighFive::DataType& datatype) {
-        return _file.createDataSet(name, filespace, datatype);
-    }
-
-    HighFive::DataSet get(const std::string& name) {
-        return _file.getDataSet(name);
-    }
-
-  private:
-    HighFive::File _file;
-};
-
-template <class Proxy>
-void check_single_string(Proxy proxy, size_t string_length) {
-    auto value = std::string(string_length, 'o');
-    auto dataspace = DataSpace::From(value);
-
-    auto n_chars = value.size() + 1;
-    auto n_chars_overlength = n_chars + 10;
-    auto fixed_length = FixedLengthStringType(n_chars, StringPadding::NullTerminated);
-    auto overlength_nullterm = FixedLengthStringType(n_chars_overlength,
-                                                     StringPadding::NullTerminated);
-    auto overlength_nullpad = FixedLengthStringType(n_chars_overlength, StringPadding::NullPadded);
-    auto overlength_spacepad = FixedLengthStringType(n_chars_overlength,
-                                                     StringPadding::SpacePadded);
-    auto variable_length = VariableLengthStringType();
-
-    SECTION("automatic") {
-        proxy.create("auto", value);
-        REQUIRE(proxy.get("auto").template read<std::string>() == value);
-    }
-
-    SECTION("fixed length") {
-        proxy.create("fixed", dataspace, fixed_length).write(value);
-        REQUIRE(proxy.get("fixed").template read<std::string>() == value);
-    }
-
-    SECTION("overlength null-terminated") {
-        proxy.create("overlength_nullterm", dataspace, overlength_nullterm).write(value);
-        REQUIRE(proxy.get("overlength_nullterm").template read<std::string>() == value);
-    }
-
-    SECTION("overlength null-padded") {
-        proxy.create("overlength_nullpad", dataspace, overlength_nullpad).write(value);
-        auto expected = std::string(n_chars_overlength, '\0');
-        expected.replace(0, value.size(), value.data());
-        REQUIRE(proxy.get("overlength_nullpad").template read<std::string>() == expected);
-    }
-
-    SECTION("overlength space-padded") {
-        proxy.create("overlength_spacepad", dataspace, overlength_spacepad).write(value);
-        auto expected = std::string(n_chars_overlength, ' ');
-        expected.replace(0, value.size(), value.data());
-        REQUIRE(proxy.get("overlength_spacepad").template read<std::string>() == expected);
-    }
-
-    SECTION("variable length") {
-        proxy.create("variable", dataspace, variable_length).write(value);
-        REQUIRE(proxy.get("variable").template read<std::string>() == value);
-    }
-}
-
-template <class Proxy>
-void check_multiple_string(Proxy proxy, size_t string_length) {
-    using value_t = std::vector<std::string>;
-    auto value = value_t{std::string(string_length, 'o'), std::string(string_length, 'x')};
-
-    auto dataspace = DataSpace::From(value);
-
-    auto string_overlength = string_length + 10;
-    auto onpoint_nullpad = FixedLengthStringType(string_length, StringPadding::NullPadded);
-    auto onpoint_spacepad = FixedLengthStringType(string_length, StringPadding::SpacePadded);
-
-    auto overlength_nullterm = FixedLengthStringType(string_overlength,
-                                                     StringPadding::NullTerminated);
-    auto overlength_nullpad = FixedLengthStringType(string_overlength, StringPadding::NullPadded);
-    auto overlength_spacepad = FixedLengthStringType(string_overlength, StringPadding::SpacePadded);
-    auto variable_length = VariableLengthStringType();
-
-    auto check = [](const value_t actual, const value_t& expected) {
-        REQUIRE(actual.size() == expected.size());
-        for (size_t i = 0; i < actual.size(); ++i) {
-            REQUIRE(actual[i] == expected[i]);
-        }
-    };
-
-    SECTION("automatic") {
-        proxy.create("auto", value);
-        check(proxy.get("auto").template read<value_t>(), value);
-    }
-
-    SECTION("variable length") {
-        proxy.create("variable", dataspace, variable_length).write(value);
-        check(proxy.get("variable").template read<value_t>(), value);
-    }
-
-    auto make_padded_reference = [&](char pad, size_t n) {
-        auto expected = std::vector<std::string>(value.size(), std::string(n, pad));
-        for (size_t i = 0; i < value.size(); ++i) {
-            expected[i].replace(0, value[i].size(), value[i].data());
-        }
-
-        return expected;
-    };
-
-    auto check_fixed_length = [&](const std::string& label, size_t length) {
-        SECTION(label + " null-terminated") {
-            auto datatype = FixedLengthStringType(length + 1, StringPadding::NullTerminated);
-            proxy.create(label + "_nullterm", dataspace, datatype).write(value);
-            check(proxy.get(label + "_nullterm").template read<value_t>(), value);
-        }
-
-        SECTION(label + " null-padded") {
-            auto datatype = FixedLengthStringType(length, StringPadding::NullPadded);
-            proxy.create(label + "_nullpad", dataspace, datatype).write(value);
-            auto expected = make_padded_reference('\0', length);
-            check(proxy.get(label + "_nullpad").template read<value_t>(), expected);
-        }
-
-        SECTION(label + " space-padded") {
-            auto datatype = FixedLengthStringType(length, StringPadding::SpacePadded);
-            proxy.create(label + "_spacepad", dataspace, datatype).write(value);
-            auto expected = make_padded_reference(' ', length);
-            check(proxy.get(label + "_spacepad").template read<value_t>(), expected);
-        }
-    };
-
-    check_fixed_length("onpoint", string_length);
-    check_fixed_length("overlength", string_length + 5);
-
-
-    SECTION("underlength null-terminated") {
-        auto datatype = FixedLengthStringType(string_length, StringPadding::NullTerminated);
-        REQUIRE_THROWS(proxy.create("underlength_nullterm", dataspace, datatype).write(value));
-    }
-
-    SECTION("underlength nullpad") {
-        auto datatype = FixedLengthStringType(string_length - 1, StringPadding::NullPadded);
-        REQUIRE_THROWS(proxy.create("underlength_nullpad", dataspace, datatype).write(value));
-    }
-
-    SECTION("underlength spacepad") {
-        auto datatype = FixedLengthStringType(string_length - 1, StringPadding::NullTerminated);
-        REQUIRE_THROWS(proxy.create("underlength_spacepad", dataspace, datatype).write(value));
-    }
-}
-
-TEST_CASE("HighFiveSTDString (dataset, single, short)") {
-    File file("std_string_dataset_single_short.h5", File::Truncate);
-    check_single_string(ForwardToDataSet(file), 3);
-}
-
-TEST_CASE("HighFiveSTDString (attribute, single, short)") {
-    File file("std_string_attribute_single_short.h5", File::Truncate);
-    check_single_string(ForwardToAttribute(file), 3);
-}
-
-TEST_CASE("HighFiveSTDString (dataset, single, long)") {
-    File file("std_string_dataset_single_long.h5", File::Truncate);
-    check_single_string(ForwardToDataSet(file), 256);
-}
-
-TEST_CASE("HighFiveSTDString (attribute, single, long)") {
-    File file("std_string_attribute_single_long.h5", File::Truncate);
-    check_single_string(ForwardToAttribute(file), 256);
-}
-
-TEST_CASE("HighFiveSTDString (dataset, multiple, short)") {
-    File file("std_string_dataset_multiple_short.h5", File::Truncate);
-    check_multiple_string(ForwardToDataSet(file), 3);
-}
-
-TEST_CASE("HighFiveSTDString (attribute, multiple, short)") {
-    File file("std_string_attribute_multiple_short.h5", File::Truncate);
-    check_multiple_string(ForwardToAttribute(file), 3);
-}
-
-TEST_CASE("HighFiveSTDString (dataset, multiple, long)") {
-    File file("std_string_dataset_multiple_long.h5", File::Truncate);
-    check_multiple_string(ForwardToDataSet(file), 256);
-}
-
-TEST_CASE("HighFiveSTDString (attribute, multiple, long)") {
-    File file("std_string_attribute_multiple_long.h5", File::Truncate);
-    check_multiple_string(ForwardToAttribute(file), 256);
-}
-
-TEST_CASE("HighFiveFixedString") {
-    const std::string file_name("array_atomic_types.h5");
-    const std::string group_1("group1");
-
-    // Create a new file using the default property lists.
-    File file(file_name, File::ReadWrite | File::Create | File::Truncate);
-    char raw_strings[][10] = {"abcd", "1234"};
-
-    /// This will not compile - only char arrays - hits static_assert with a nice
-    /// error
-    // file.createDataSet<int[10]>(ds_name, DataSpace(2)));
-
-    {  // But char should be fine
-        auto ds = file.createDataSet<char[10]>("ds1", DataSpace(2));
-        CHECK(ds.getDataType().getClass() == DataTypeClass::String);
-        ds.write(raw_strings);
-    }
-
-    {  // char[] is, by default, int8
-        auto ds2 = file.createDataSet("ds2", raw_strings);
-        CHECK(ds2.getDataType().getClass() == DataTypeClass::Integer);
-    }
-
-    {  // String Truncate happens low-level if well setup
-        auto ds3 = file.createDataSet<char[6]>("ds3", DataSpace::FromCharArrayStrings(raw_strings));
-        ds3.write(raw_strings);
-    }
-
-    {  // Write as raw elements from pointer (with const)
-        const char(*strings_fixed)[10] = raw_strings;
-        // With a pointer we dont know how many strings -> manual DataSpace
-        file.createDataSet<char[10]>("ds4", DataSpace(2)).write(strings_fixed);
-    }
-
-
-    {  // Cant convert flex-length to fixed-length
-        const char* buffer[] = {"abcd", "1234"};
-        SilenceHDF5 silencer;
-        CHECK_THROWS_AS(file.createDataSet<char[10]>("ds5", DataSpace(2)).write(buffer),
-                        HighFive::DataSetException);
-    }
-
-    {  // scalar char strings
-        const char buffer[] = "abcd";
-        file.createDataSet<char[10]>("ds6", DataSpace(1)).write(buffer);
-    }
-
-    {
-        // Direct way of writing `std::string` as a fixed length
-        // HDF5 string.
-
-        std::string value = "foo";
-        auto n_chars = value.size() + 1;
-
-        auto datatype = FixedLengthStringType(n_chars, StringPadding::NullTerminated);
-        auto dataspace = DataSpace(1);
-
-        auto ds = file.createDataSet("ds8", dataspace, datatype);
-        ds.write_raw(value.data(), datatype);
-
-        {
-            // Due to missing non-const overload of `data()` until C++17 we'll
-            // read into something else instead (don't forget the '\0').
-            auto expected = std::vector<char>(n_chars, '!');
-            ds.read_raw(expected.data(), datatype);
-
-            CHECK(expected.size() == value.size() + 1);
-            for (size_t i = 0; i < value.size(); ++i) {
-                REQUIRE(expected[i] == value[i]);
-            }
-        }
-
-#if HIGHFIVE_CXX_STD >= 17
-        {
-            auto expected = std::string(value.size(), '-');
-            ds.read_raw(expected.data(), datatype);
-
-            REQUIRE(expected == value);
-        }
-#endif
-    }
-
-    {
-        size_t n_chars = 4;
-        size_t n_strings = 2;
-
-        std::vector<char> value(n_chars * n_strings, '!');
-
-        auto datatype = FixedLengthStringType(n_chars, StringPadding::NullTerminated);
-        auto dataspace = DataSpace(n_strings);
-
-        auto ds = file.createDataSet("ds9", dataspace, datatype);
-        ds.write_raw(value.data(), datatype);
-
-        auto expected = std::vector<char>(value.size(), '-');
-        ds.read_raw(expected.data(), datatype);
-
-        CHECK(expected.size() == value.size());
-        for (size_t i = 0; i < value.size(); ++i) {
-            REQUIRE(expected[i] == value[i]);
-        }
-    }
-}
-
 TEST_CASE("HighFiveReference") {
     const std::string file_name("h5_ref_test.h5");
     const std::string dataset1_name("dset1");
diff --git a/packages/HighFive/tests/unit/tests_high_five_easy.cpp b/packages/HighFive/tests/unit/tests_high_five_easy.cpp
index aa30b4e96..d10ef941b 100644
--- a/packages/HighFive/tests/unit/tests_high_five_easy.cpp
+++ b/packages/HighFive/tests/unit/tests_high_five_easy.cpp
@@ -243,6 +243,44 @@ TEST_CASE("H5Easy_xtensor") {
     CHECK(xt::all(xt::equal(B, B_r)));
 }
 
+TEST_CASE("H5Easy_xtensor_column_major") {
+    H5Easy::File file("h5easy_xtensor_colum_major.h5", H5Easy::File::Overwrite);
+
+    using column_major_t = xt::xtensor<double, 2, xt::layout_type::column_major>;
+
+    xt::xtensor<double, 2> A = 100. * xt::random::randn<double>({20, 5});
+
+    H5Easy::dump(file, "/path/to/A", A);
+
+    SECTION("Write column major") {
+        column_major_t B = A;
+        REQUIRE_THROWS(H5Easy::dump(file, "path/to/B", B));
+    }
+
+    SECTION("Read column major") {
+        REQUIRE_THROWS(H5Easy::load<column_major_t>(file, "/path/to/A"));
+    }
+}
+
+TEST_CASE("H5Easy_xarray_column_major") {
+    H5Easy::File file("h5easy_xarray_colum_major.h5", H5Easy::File::Overwrite);
+
+    using column_major_t = xt::xarray<double, xt::layout_type::column_major>;
+
+    xt::xarray<double> A = 100. * xt::random::randn<double>({20, 5});
+
+    H5Easy::dump(file, "/path/to/A", A);
+
+    SECTION("Write column major") {
+        column_major_t B = A;
+        REQUIRE_THROWS(H5Easy::dump(file, "path/to/B", B));
+    }
+
+    SECTION("Read column major") {
+        REQUIRE_THROWS(H5Easy::load<column_major_t>(file, "/path/to/A"));
+    }
+}
+
 TEST_CASE("H5Easy_xarray") {
     H5Easy::File file("h5easy_xarray.h5", H5Easy::File::Overwrite);
 
diff --git a/packages/HighFive/tests/unit/tests_high_five_multi_dims.cpp b/packages/HighFive/tests/unit/tests_high_five_multi_dims.cpp
index 60ec66cae..a261360e0 100644
--- a/packages/HighFive/tests/unit/tests_high_five_multi_dims.cpp
+++ b/packages/HighFive/tests/unit/tests_high_five_multi_dims.cpp
@@ -169,6 +169,15 @@ TEMPLATE_LIST_TEST_CASE("MultiArray3D", "[template]", numerical_test_types) {
     MultiArray3DTest<TestType>();
 }
 
+TEST_CASE("Test boost::multi_array with fortran_storage_order") {
+    const std::string file_name("h5_multi_array_fortran.h5");
+    File file(file_name, File::ReadWrite | File::Create | File::Truncate);
+
+    boost::multi_array<int, 2> ma(boost::extents[2][2], boost::fortran_storage_order());
+    auto dset = file.createDataSet<int>("main_dset", DataSpace::From(ma));
+    CHECK_THROWS_AS(dset.write(ma), DataTypeException);
+}
+
 template <typename T>
 void ublas_matrix_Test() {
     using Matrix = boost::numeric::ublas::matrix<T>;
-- 
GitLab