Skip to content

Commit

Permalink
Support boost::span. (#1025)
Browse files Browse the repository at this point in the history
  • Loading branch information
1uc authored Dec 2, 2024
1 parent d7da804 commit dea3eab
Show file tree
Hide file tree
Showing 9 changed files with 211 additions and 97 deletions.
11 changes: 6 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -189,13 +189,13 @@ jobs:
# Job running unit-test with sanitizers
# =====================================
Linux_Sanitizers:
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
strategy:
matrix:
env: [
{CC: clang-12, CXX: clang++-12, HIGHFIVE_SANITIZER: address},
{CC: clang-12, CXX: clang++-12, HIGHFIVE_SANITIZER: undefined},
{CC: gcc-10, CXX: g++-10, HIGHFIVE_GLIBCXX_ASSERTIONS: On},
{CC: clang, CXX: clang++, HIGHFIVE_SANITIZER: address},
{CC: clang, CXX: clang++, HIGHFIVE_SANITIZER: undefined},
{CC: gcc, CXX: g++, HIGHFIVE_GLIBCXX_ASSERTIONS: On},
]

steps:
Expand All @@ -206,14 +206,15 @@ jobs:
- name: "Install libraries"
run: |
sudo apt-get -qq update
sudo apt-get -qq install libboost-all-dev libeigen3-dev libhdf5-dev libsz2 ninja-build
sudo apt-get -qq install boost1.83 libeigen3-dev libhdf5-dev libsz2 ninja-build
- name: Build
env: ${{matrix.env}}
run: |
CMAKE_OPTIONS=(
-GNinja
-DHIGHFIVE_TEST_BOOST:BOOL=ON
-DHIGHFIVE_TEST_BOOST_SPAN:BOOL=ON
-DHIGHFIVE_TEST_EIGEN:BOOL=ON
-DHIGHFIVE_BUILD_DOCS:BOOL=FALSE
-DHIGHFIVE_GLIBCXX_ASSERTIONS=${HIGHFIVE_GLIBCXX_ASSERTIONS:-OFF}
Expand Down
13 changes: 7 additions & 6 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,13 @@ 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)
option(HIGHFIVE_TEST_XTENSOR "Enable xtensor testing" OFF)
option(HIGHFIVE_TEST_HALF_FLOAT "Enable half-precision floats" OFF)
option(HIGHFIVE_TEST_SPAN "Enable testing std::span, requires C++20" ${HIGHFIVE_TEST_SPAN_DEFAULT})
option(HIGHFIVE_TEST_BOOST "Enable testing Boost features" OFF)
option(HIGHFIVE_TEST_BOOST_SPAN "Additionally, enable testing `boost::span`" OFF)
option(HIGHFIVE_TEST_EIGEN "Enable testing Eigen" OFF)
option(HIGHFIVE_TEST_OPENCV "Enable testing OpenCV" OFF)
option(HIGHFIVE_TEST_XTENSOR "Enable testing xtensor" OFF)
option(HIGHFIVE_TEST_HALF_FLOAT "Enable testing half-precision floats" OFF)

# TODO remove entirely.
option(HIGHFIVE_HAS_CONCEPTS "Print readable compiler errors w/ C++20 concepts" OFF)
Expand Down
3 changes: 3 additions & 0 deletions cmake/HighFiveOptionalDependencies.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ if(NOT TARGET HighFiveBoostDependency)
# -DBOOST_ALL_NO_LIB (does something on MSVC).
target_compile_definitions(HighFiveBoostDependency INTERFACE HIGHFIVE_TEST_BOOST=1)
endif()
if(HIGHFIVE_TEST_BOOST_SPAN)
target_compile_definitions(HighFiveBoostDependency INTERFACE HIGHFIVE_TEST_BOOST_SPAN=1)
endif()
endif()

if(NOT TARGET HighFiveEigenDependency)
Expand Down
98 changes: 98 additions & 0 deletions include/highfive/bits/inspector_stl_span_misc.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#pragma once

#include "H5Inspector_decl.hpp"
#include "../H5Exception.hpp"

#include <cstdlib>
#include <vector>
#include <type_traits>

namespace HighFive {
namespace details {


// Anything with the same API as `std::span` can implemented by inheriting from
// this class.
template <class Span>
struct inspector_stl_span {
using type = Span;
using value_type = unqualified_t<typename Span::value_type>;
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
24 changes: 24 additions & 0 deletions include/highfive/boost_span.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#pragma once

#include "bits/H5Inspector_decl.hpp"
#include "H5Exception.hpp"
#include "bits/inspector_stl_span_misc.hpp"

#include <boost/core/span.hpp>

namespace HighFive {
namespace details {
template <class T, std::size_t Extent>
struct inspector<boost::span<T, Extent>>: public inspector_stl_span<boost::span<T, Extent>> {
private:
using super = inspector_stl_span<boost::span<T, Extent>>;

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
89 changes: 10 additions & 79 deletions include/highfive/span.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,92 +10,23 @@
#pragma once

#include "bits/H5Inspector_decl.hpp"
#include "H5Exception.hpp"
#include "bits/inspector_stl_span_misc.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]);
}
}
struct inspector<std::span<T, Extent>>: public inspector_stl_span<std::span<T, Extent>> {
private:
using super = inspector_stl_span<std::span<T, Extent>>;

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
Expand Down
4 changes: 4 additions & 0 deletions tests/unit/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ foreach(PUBLIC_HEADER ${public_headers})
continue()
endif()

if(PUBLIC_HEADER STREQUAL "highfive/boost_span.hpp" AND NOT HIGHFIVE_TEST_BOOST_SPAN)
continue()
endif()

if(PUBLIC_HEADER STREQUAL "highfive/half_float.hpp" AND NOT HIGHFIVE_TEST_HALF_FLOAT)
continue()
endif()
Expand Down
49 changes: 42 additions & 7 deletions tests/unit/data_generator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
#include <highfive/boost.hpp>
#endif

#ifdef HIGHFIVE_TEST_BOOST_SPAN
#include <highfive/boost_span.hpp>
#endif

#ifdef HIGHFIVE_TEST_EIGEN
#include <highfive/eigen.hpp>
#endif
Expand Down Expand Up @@ -236,12 +240,15 @@ 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>> {
// Anything with the same API as `std::span` can implemented by inheriting from
// this class.
//
// The template parameter `DynamicExtent` is the equivalent of the magic number
// `std::dynamic_extent`.
template <class Span, size_t DynamicExtent>
struct STLSpanLikeContainerTraits: public STLLikeContainerTraits<Span> {
private:
using super = STLLikeContainerTraits<std::span<T, Extent>>;
using super = STLLikeContainerTraits<Span>;

public:
using container_type = typename super::container_type;
Expand Down Expand Up @@ -274,12 +281,26 @@ struct ContainerTraits<std::span<T, Extent>>: public STLLikeContainerTraits<std:
}

static void sanitize_dims(std::vector<size_t>& dims, size_t axis) {
if (Extent != std::dynamic_extent) {
dims[axis] = Extent;
if (Span::extent != DynamicExtent) {
dims[axis] = Span::extent;
ContainerTraits<value_type>::sanitize_dims(dims, axis + 1);
}
}
};


#ifdef HIGHFIVE_TEST_SPAN
template <class T, std::size_t Extent>
struct ContainerTraits<std::span<T, Extent>>
: public STLSpanLikeContainerTraits<std::span<T, Extent>, std::dynamic_extent> {
private:
using super = STLSpanLikeContainerTraits<std::span<T, Extent>, std::dynamic_extent>;

public:
using container_type = typename super::container_type;
using value_type = typename super::value_type;
using base_type = typename super::base_type;
};
#endif


Expand Down Expand Up @@ -428,6 +449,20 @@ struct ContainerTraits<boost::numeric::ublas::matrix<T>> {

#endif

#if HIGHFIVE_TEST_BOOST_SPAN
template <class T, std::size_t Extent>
struct ContainerTraits<boost::span<T, Extent>>
: public STLSpanLikeContainerTraits<boost::span<T, Extent>, boost::dynamic_extent> {
private:
using super = STLSpanLikeContainerTraits<boost::span<T, Extent>, boost::dynamic_extent>;

public:
using container_type = typename super::container_type;
using value_type = typename super::value_type;
using base_type = typename super::base_type;
};
#endif

// -- Eigen -------------------------------------------------------------------
#if HIGHFIVE_TEST_EIGEN

Expand Down
Loading

0 comments on commit dea3eab

Please sign in to comment.