Status legend (project convention):
| Symbol | Meaning |
| ✔ ok | round-trips correctly today; on-disk shape and values both preserved |
| ✘ fail | path is reachable but has a library-level defect (value or shape diverges, throws, or hard-faults) |
| ◇ na | unsupported by design; the compile-time stopper fires correctly (no silent failure on disk) |
The table reports what the code does now. The proposed H5T_ARRAY / fixed-length-string-element model is documented separately in tasks/h5cpp-type-system-architecture-notes.md.
1. Native arithmetic types
| C++ source type | HDF5 type | Status |
bool (scalar) | H5T_NATIVE_HBOOL, scalar dataspace | ✔ ok |
Integer types — signed (char† / signed char / short / int / long / long long) | H5T_NATIVE_CHAR / H5T_NATIVE_SCHAR‡ / H5T_NATIVE_SHORT / H5T_NATIVE_INT / H5T_NATIVE_LONG / H5T_NATIVE_LLONG, scalar dataspace | ✔ ok |
Integer types — unsigned (unsigned char / unsigned short / unsigned int / unsigned long / unsigned long long) | H5T_NATIVE_UCHAR / H5T_NATIVE_USHORT / H5T_NATIVE_UINT / H5T_NATIVE_ULONG / H5T_NATIVE_ULLONG, scalar dataspace | ✔ ok |
Floating-point (float / double / long double) | H5T_NATIVE_FLOAT / H5T_NATIVE_DOUBLE / H5T_NATIVE_LDOUBLE, scalar dataspace | ✔ ok |
Half-float — half_float::half (vendored, gated on HALF_HALF_HPP) | H5Tcopy(H5T_NATIVE_FLOAT) + H5Tset_fields/H5Tset_size(2) (16-bit IEEE half) | ✔ ok |
Half-float — OPENEXR::half (gated on WITH_OPENEXR_HALF) | Same 16-bit IEEE half representation | ✔ ok |
Half-float — std::float16_t (C++23, gated on __STDCPP_FLOAT16_T__) | Same 16-bit IEEE half representation | ✔ ok |
enum / enum class (any underlying integer type) | H5T_ENUM over the underlying integer; named enumerators inserted via explicit H5CPP_REGISTER_DATATYPE(EnumT, "name", H5Tenum_create(H5T_NATIVE_*), { … H5Tenum_insert … }) macro invocation in user code | ✔ ok (via explicit H5CPP_REGISTER_DATATYPE; h5cpp-compiler does not auto-emit enum registrations today — the macro must be written by hand) |
† char has implementation-defined signedness; dt_t<char> is H5T_NATIVE_CHAR (the platform's choice). For text containers see §4. ‡ When signed char and char happen to coincide on a platform, dt_t<signed char> resolves to H5T_NATIVE_SCHAR; the two types remain distinct in the C++ type system regardless.
2. Other primitive / opaque elements
| C++ source type | HDF5 type | Status |
h5::reference_t (object / region reference) | H5T_STD_REF (HDF5 ≥ 1.12) or H5T_STD_REF_DSETREG (older), scalar dataspace | ✔ ok |
std::complex<float> / std::complex<double> / std::complex<long double> (scalar) | H5T_COMPLEX (HDF5 ≥ 2.0, via H5Tcomplex_create) or two-field H5T_COMPOUND { r, i } fallback for HDF5 < 2.0 (gated on H5_VERSION_GE(2,0,0)), scalar dataspace | ✔ ok |
3. Compound POD structs — Tier-1 (compiler-assisted)
POD struct registered via H5CPP_REGISTER_STRUCT(T). The compiler emits a register_struct<T>() body that builds the H5T_COMPOUND with H5Tinsert for each field at its HOFFSET. Trivially copyable, standard-layout. See examples/compound/, examples/csv/, examples/multi-tu/, examples/packet-table/.
| C++ source type | HDF5 type | Status |
Registered POD struct (e.g. sn::example::Record) — top-level scalar | H5T_COMPOUND { … fields … }, scalar dataspace | ✔ ok |
std::vector<RegisteredPOD> | rank-1 dataspace of H5T_COMPOUND elements | ✔ ok |
Per-field char[N] inside a registered POD (e.g. input_t::ReportedLocation in csv) | H5T_ARRAY[N] H5T_NATIVE_CHAR within the compound | ✔ ok |
Per-field nested POD struct (e.g. sn::example::Record::field_03) | Nested H5T_COMPOUND within the parent compound | ✔ ok |
Per-field array of nested POD (e.g. Record::field_05[3][8]) | Nested H5T_ARRAY of H5T_ARRAY of H5T_COMPOUND | ✔ ok |
| Unregistered POD aggregate | — | ◇ na (storage = unsupported; static_assert directs caller to H5CPP_REGISTER_STRUCT) |
4. Text / string types
| C++ source type | HDF5 type | Status |
std::string (scalar) | H5T_C_S1 + H5Tset_size(H5T_VARIABLE) + H5Tset_cset(H5T_CSET_UTF8), scalar dataspace | ✔ ok |
std::string_view (write-only, read into std::string) | Same variable-length string type, scalar dataspace | ✔ ok |
char* / const char* | H5T_C_S1 + H5T_VARIABLE, scalar dataspace | ✔ ok |
char[N] (top-level) | H5T_C_S1 + H5Tset_size(N) (fixed-length string), scalar dataspace | ✔ ok |
std::array<char, N> (top-level) | H5T_C_S1 + H5Tset_size(N), scalar dataspace (fixed_length_string) | ✔ ok |
signed char[N] / unsigned char[N] (top-level, non-char element type) | rank-1 dataspace of N H5T_NATIVE_SCHAR / H5T_NATIVE_UCHAR (c_array storage) | ✔ ok (byte arrays as intended) |
wchar_t[N], char16_t[N], char32_t[N] (wide-char strings) | — | ◇ na (HDF5 vlen string is 8-bit; no wide-char path) |
std::vector<std::string> | rank-1 dataspace of H5T_C_S1 + H5T_VARIABLE elements (vlen_text_dataset) | ✔ ok |
std::list<std::string> / std::set<std::string> / etc. | rank-1 dataspace of vlen strings via iterator staging | ✔ ok (structural fallback) |
std::basic_string<char16_t> / <char32_t> / <wchar_t> | — | ◇ na |
5. STL fixed-extent containers (canonical model — H5T_ARRAY element)
Canonical Winston model: fixed-extent compile-time shape lives in the element type (H5T_ARRAY[N]), not in the dataspace. Top-level fixed-extent containers therefore land as a scalar dataspace carrying a single H5T_ARRAY element.
| C++ source type | HDF5 type | Status |
std::array<T, N> (T non-char arithmetic) | scalar dataspace, H5T_ARRAY[N] dt_t<T> element (array_element) | ✔ ok |
T[N] (T non-char arithmetic) | scalar dataspace, H5T_ARRAY[N] dt_t<T> element (array_element) | ✔ ok |
T[N][M] / T[N][M][P] / … up to rank 7 | scalar dataspace, multi-dim H5T_ARRAY element | ✔ ok |
std::array<std::array<T, N>, M> (nested fixed) | scalar dataspace, H5T_ARRAY[M] H5T_ARRAY[N] dt_t<T> (array_element) | ✔ ok |
6. STL sequence containers (variable-extent → rank-1 dataspace + hyperslab)
| C++ source type | HDF5 type | Status |
std::vector<T> (T arithmetic / complex / POD) | rank-1 dataspace of dt_t<T> (linear_value_dataset) | ✔ ok |
std::deque<T> (T arithmetic) | rank-1 dataspace via iterator-staging buffer (linear_value_dataset) | ✔ ok |
std::list<T> (T arithmetic) | rank-1 dataspace via iterator-staging buffer | ✔ ok |
std::forward_list<T> (T arithmetic) | rank-1 dataspace via iterator-staging buffer | ✔ ok |
std::vector<bool> | — | ◇ na (bit-packed, no contiguous bool*; storage = unsupported) |
7. STL set-shaped containers
| C++ source type | HDF5 type | Status |
std::set<T> / std::multiset<T> | rank-1 dataspace of dt_t<T> via iterator-staging (linear_value_dataset) | ✔ ok |
std::unordered_set<T> / std::unordered_multiset<T> | rank-1 dataspace of dt_t<T> via iterator-staging | ✔ ok (insertion order not preserved across round-trip) |
8. STL map-shaped containers (compound key-value)
| C++ source type | HDF5 type | Status |
std::map<K, V> / std::multimap<K, V> | rank-1 dataspace of H5T_COMPOUND { K key; V value } (key_value_dataset) | ✔ ok |
std::unordered_map<K, V> / std::unordered_multimap<K, V> | Same H5T_COMPOUND compound, rank-1 dataspace | ✔ ok (insertion order not preserved) |
9. STL tuple / pair (scalar + container forms)
| C++ source type | HDF5 type | Status |
std::tuple<arith, arith, …> (scalar; all arithmetic / trivially-copyable fields) | H5T_COMPOUND { _0, _1, … } with tuple_layout offsets, scalar dataspace (composite kind) | ✔ ok |
std::tuple<…, std::string, …> (scalar; any non-trivial field) | H5T_COMPOUND with vlen-string slot at offset, but tuple_layout::to_buffer memcpys the std::string object — layout mismatch | ✘ fail (string fields land as garbage; documented in examples/stl/README.md) |
std::vector<std::tuple<…>> (arithmetic fields) | rank-1 dataspace of H5T_COMPOUND (linear_value_dataset, composite element) | ✔ ok |
std::list<std::tuple<…>> / std::deque<…> / std::forward_list<…> (arithmetic fields) | rank-1 dataspace of H5T_COMPOUND via iterator staging | ✔ ok |
std::set<std::tuple<…>> / std::multiset<…> (arithmetic fields) | rank-1 dataspace of H5T_COMPOUND via iterator staging | ✔ ok |
std::pair<K, V> (scalar) | H5T_COMPOUND { first, second } via dt_t<pair> + offsetof, scalar dataspace (object kind) | ✔ ok |
std::vector<std::pair<K, V>> (trivially-copyable K, V) | rank-1 dataspace of H5T_COMPOUND { first, second } | ✔ ok |
10. STL nested containers
| C++ source type | HDF5 type | Status |
std::vector<std::vector<T>> (ragged, T flat) | rank-1 dataspace of H5T_VLEN dt_t<T> (ragged_vlen_dataset) via hvl_t relay | ✔ ok |
std::vector<std::list<T>> / std::deque<T> / std::forward_list<T> (T flat) | rank-1 dataspace of H5T_VLEN dt_t<T> via per-element scratch buffer + hvl_t relay | ✔ ok |
std::vector<std::set<T>> / std::multiset<T> / std::unordered_set<T> (T flat) | Same ragged_vlen path | ✔ ok |
std::vector<std::array<T, N>> (T non-char arithmetic) | rank-1 dataspace, H5T_ARRAY[N] dt_t<T> element (array_dataset) | ✔ ok |
std::vector<std::array<char, N>> | rank-1 dataspace, H5T_C_S1 + H5Tset_size(N) element (fls_dataset) | ✔ ok |
std::list<std::array<T, N>>, std::deque<std::array<...>>, std::set<std::array<...>>, etc. (T non-char) | rank-1 dataspace, H5T_ARRAY[N] dt_t<T> element (array_dataset, iterator-staged) | ✔ ok |
Iterable outer container Outer<std::array<char, N>> | rank-1 dataspace, H5T_C_S1 + H5Tset_size(N) element (fls_dataset) | ✔ ok |
std::vector<std::list<std::list<T>>> (3+ levels of nesting) | — | ◇ na (nested-container stopper) |
std::array<std::string, N> / std::array<std::vector<T>, N> | — | ◇ na (array-of-container guard from #274) |
11. Walter Brown structural fallback (custom non-std:: containers)
Detection idiom in H5Tmeta.hpp (is_sequential_like, is_set_like, is_map_like, has_data_pointer, has_size, has_iterator, is_transport_contiguous_v). Triggers for any user-defined container that exposes the relevant member typedefs and methods, and is NOT in has_explicit_storage_repr<T>.
| C++ source type | HDF5 type | Status |
Custom contiguous container with .data() + .size() + value_type (e.g. absl::FixedArray<T>, folly::small_vector<T>, boost::container::vector<T>, user-defined) | rank-1 dataspace of dt_t<T> via generic access_traits_t contiguous spec | ✔ ok (write); read works when the type has a T(size_t) ctor |
Custom iterator-only sequence with begin/end + value_type (no .data()) | rank-1 dataspace via iterator-staging (linear_value_dataset) | ✔ ok (write); read assigns into a range-constructible target — generic non-std:: reconstruction is best-effort today |
Custom set-shape with key_type + value_type + key_compare (no mapped_type) | rank-1 dataspace via iterator-staging (linear_value_dataset) | ✔ ok (write); read inserts via insert(iter, iter) if available |
Custom map-shape with key_type + mapped_type + value_type | rank-1 dataspace of H5T_COMPOUND { key, value } (key_value_dataset) | ✔ ok (write); read inserts pairs |
boost::container::flat_map, flat_set, ankerl::unordered_dense::map | Same as their std:: counterparts via structural detection | ✔ ok (untested in tree, expected by design) |
12. Smart pointers (added in this branch)
| C++ source type | HDF5 type | Status |
std::unique_ptr<T[]> (with explicit h5::count{...}) | Forwards to raw-T* path; same as the underlying T array's HDF5 type | ✔ ok |
std::shared_ptr<T[]> (with explicit h5::count{...}) | Same — forwards to raw-T* path | ✔ ok |
std::unique_ptr<T> (single-element) | Synthetic h5::count{1}; same HDF5 type as T in a rank-1 size-1 dataset | ✔ ok |
std::shared_ptr<T> (single-element) | Same as unique_ptr<T> (single) | ✔ ok |
Auto-allocating return-style h5::read<std::unique_ptr<T[]>>(fd, path) | Sizes the unique_ptr to the dataset extent via impl::get<>::ctor; same HDF5 type as T* | ✔ ok |
std::unique_ptr<RegisteredPOD[]> (compose with H5CPP_REGISTER_STRUCT) | rank-1 dataspace of H5T_COMPOUND elements | ✔ ok |
13. C++23 std::mdspan (gated on __cpp_lib_mdspan)
| C++ source type | HDF5 type | Status |
std::mdspan<T, std::extents<…>> (compile-time static extents) | rank-N dataspace of dt_t<T> matching the extents | ✔ ok |
std::mdspan<T, std::dextents<…>> (dynamic extents) | rank-N dataspace of dt_t<T> with runtime extents | ✔ ok |
std::mdspan with non-default LayoutPolicy (e.g. layout_left, layout_stride) | Treated as layout_right (C-style row-major) | partial — non-default layouts coerced; documented in mapper |
14. Linear-algebra backends
14.1 Armadillo (gated on ARMA_INCLUDES / H5CPP_USE_ARMADILLO)
| C++ source type | HDF5 type | Status |
arma::Row<T> (arma::rowvec) | rank-1 dataspace of N dt_t<T> | ✔ ok |
arma::Col<T> (arma::colvec / arma::vec) | rank-1 dataspace of N dt_t<T> | ✔ ok |
arma::Mat<T> (arma::mat) | rank-2 dataspace (n_rows, n_cols) of dt_t<T> (column-major on disk) | ✔ ok |
arma::Cube<T> | rank-3 dataspace (n_slices, n_cols, n_rows) of dt_t<T> | ✔ ok |
14.2 Eigen3 (gated on Eigen/Dense include or H5CPP_USE_EIGEN3)
| C++ source type | HDF5 type | Status |
Eigen::Matrix<T, Dynamic, Dynamic, RowMajor> | rank-2 dataspace (rows, cols) of dt_t<T> | ✔ ok |
Eigen::Matrix<T, Dynamic, Dynamic, ColMajor> | rank-2 dataspace (rows, cols) of dt_t<T> | ✔ ok |
Eigen::Array<T, Dynamic, Dynamic, …> | rank-2 dataspace (rows, cols) of dt_t<T> | ✔ ok |
Eigen::Matrix<T, Dynamic, 1, …> (column vector) | rank-2 dataspace (rows, 1) of dt_t<T> | ✔ ok |
Fixed-size Eigen matrices (Eigen::Matrix<T, R, C> with R,C compile-time) | — | ◇ na — pointer access path bypasses fixed-size; cast to Dynamic first |
14.3 Blaze (gated on _BLAZE_MATH_MODULE_H_ or _BLAZE_MATH_DYNAMICMATRIX_H_ or H5CPP_USE_BLAZE)
Compiled with BLAZE_USE_PADDING=0 so data() exposes packed storage.
| C++ source type | HDF5 type | Status |
blaze::DynamicVector<T, rowVector> (blaze::rowvec) | rank-1 dataspace of N dt_t<T> | ✔ ok |
blaze::DynamicVector<T, columnVector> (blaze::colvec) | rank-1 dataspace of N dt_t<T> | ✔ ok |
blaze::DynamicMatrix<T, rowMajor> (blaze::rowmat) | rank-2 dataspace (rows, columns) of dt_t<T> | ✔ ok |
blaze::DynamicMatrix<T, columnMajor> (blaze::colmat) | rank-2 dataspace (rows, columns) of dt_t<T> | ✔ ok |
14.4 Blitz++ (gated on BZ_NAMESPACE_START or H5CPP_USE_BLITZ)
| C++ source type | HDF5 type | Status |
blitz::Array<T, 1> | rank-1 dataspace of N dt_t<T> | ✔ ok |
blitz::Array<T, 2> | rank-2 dataspace (rows, cols) of dt_t<T> | ✔ ok |
blitz::Array<T, 3> | rank-3 dataspace (extent_0, extent_1, extent_2) of dt_t<T> | ✔ ok |
blitz::Array<T, N> (N up to H5CPP_MAX_RANK = 7) | rank-N dataspace of dt_t<T> | ✔ ok |
14.5 Dlib (gated on DLIB_MATRIx_HEADER or H5CPP_USE_DLIB)
| C++ source type | HDF5 type | Status |
dlib::matrix<T> (dynamic) | rank-2 dataspace (nr, nc) of dt_t<T> | ✔ ok |
dlib::matrix<T, R, C> (compile-time fixed) | — | ◇ na — same constraint as Eigen fixed-size |
14.6 Boost.uBLAS (gated on _BOOST_UBLAS_VECTOR_ / _BOOST_UBLAS_MATRIX_ or H5CPP_USE_UBLAS_VECTOR/MATRIX)
| C++ source type | HDF5 type | Status |
boost::numeric::ublas::vector<T> | rank-1 dataspace of N dt_t<T> | ✔ ok |
boost::numeric::ublas::matrix<T> (row-major default) | rank-2 dataspace (size1, size2) of dt_t<T> | ✔ ok |
14.7 std::valarray
| C++ source type | HDF5 type | Status |
std::valarray<T> | rank-1 dataspace of N dt_t<T> (linear_value_dataset) | ✔ ok |
14.8 xtensor / xtensor-blas (gated on XTENSOR_ARRAY_HPP, XTENSOR_XARRAY_HPP, H5CPP_USE_XTENSOR)
| C++ source type | HDF5 type | Status |
xt::xtensor<T, N> (static rank) | rank-N dataspace of dt_t<T> | ✔ ok |
xt::xarray<T> (dynamic rank) | rank-N dataspace of dt_t<T> (rank known at runtime) | ✘ fail — blocked on traits::size cross-tag conversion between count_t and current_dims_t (see examples/linalg/README.md) |
xt::xtensor_fixed<T, shape> | — | ◇ na — fixed-shape, not exercised |
xtensor-blas reductions (xt::linalg::* returning a xt::xarray) | Same as xt::xarray<T> | ✘ fail (same blocker) |
14.9 IT++ (vendored, currently disabled in CMake)
| C++ source type | HDF5 type | Status |
itpp::Mat<T>, itpp::Vec<T> | rank-2 / rank-1 dataspace of dt_t<T> | ◇ na — IT++ v4.3.1 isn't C++17-clean (uses removed register keyword); CMake target commented out |
15. Compiler-assisted Tier-2 — scatter/gather for compound w/ vlen fields
Tier-2 path for POD structs containing non-POD fields (std::string, std::vector<double>, etc.). The compiler emits a scatter<T> write specialization and a gather<T> read specialization that handle the per-field vlen serialisation. Routed via h5::scatter(fd, path, ref) / h5::gather(fd, path, ref), OR — when the user calls h5::write(fd, path, ref) / h5::read(fd, path, ref) and has_scatter<T>::value — auto-dispatched through the fd-gateway. See examples/compound/compound.cpp (sn::sensor::timeseries_t).
| C++ source type | HDF5 type | Status |
POD struct with std::string field (compiler-registered) | H5T_COMPOUND with H5T_C_S1 + H5T_VARIABLE field per std::string; scatter writes per-record | ✔ ok (via h5::scatter / h5::gather) |
POD struct with std::vector<T> field | H5T_COMPOUND with H5T_VLEN dt_t<T> field; scatter writes vlen blob per record | ✔ ok |
POD struct with [[h5::ignore]] annotated field | Field omitted from the compound; not persisted (e.g. timeseries_t::internal_id) | ✔ ok |
Calling h5::write(ds, ref) (ds-overload) on a scatter type | — | ◇ na — stopper fires: scatter types must use h5::write(fd, path, ref) to reach the generated specialization |
Tier-2 type containing nested non-POD field (e.g. std::vector<std::string> field) | Per-field VLEN with inner vlen-string compound type | ✔ ok (compiler emits the nested wiring) |
16. Unsupported by design (compile-time stoppers fire correctly)
These are NOT bugs; they're the design boundary. The static_assert at H5Dwrite.hpp / H5Dread.hpp / H5Awrite.hpp / H5Aread.hpp names the offending type and routes the developer to the right path.
| C++ source type | Stopper message |
std::vector<bool> | storage_representation_v<T> resolved to unsupported (bit-packed; no contiguous bool*) |
std::array<std::string, N> / std::array<std::vector<T>, N> | storage_representation_v<T> resolved to unsupported (array-of-container guard from #274) |
std::vector<std::list<std::list<T>>> / 3+-level nesting | containers of containers are only supported for vector<string> (vlen_text_dataset) or vector<vector<T>> (ragged_vlen_dataset) |
std::list<std::array<T, N>> / std::set<std::array<T, N>> | Same nested-container stopper (only vector<array<T,N>> is wired) |
Unregistered POD aggregate (no H5CPP_REGISTER_STRUCT) | storage_representation_v<T> resolved to unsupported. Check: unregistered POD aggregate (use H5CPP_REGISTER_STRUCT) |
Scatter type via h5::write(ds, ref) (ds-overload, not fd-gateway) | scatter types must use h5::write(fd, path, ref) so the compiler-generated h5::scatter<T> specialization can dispatch |
| Iterator-staging path with non-standard-layout element_t | std::is_standard_layout_v<element_t> static_assert |
wchar_t, char16_t, char32_t strings | — (no path registered; resolves to unsupported) |
std::basic_string<signed char> / <unsigned char> | — (not idiomatic; falls to unsupported) |
17. h5cpp-compiler reflection attributes
The Clang-based h5cpp compiler tool scans user headers and emits generated.h containing the H5CPP_REGISTER_STRUCT(T) bodies and (for Tier-2) the scatter<T> / gather<T> specialisations. Per-struct and per-field behaviour is steered by [[h5::*]] attributes on the source declaration. See examples/reflection/ for end-to-end demos.
The attribute column distinguishes struct-level (applied to the type declaration) from field-level (applied to one data member); they're not interchangeable.
| Attribute | Scope | Effect on emitted code | Status |
[[h5::doc("text")]] | struct or field | Documentation string propagated into generated.h (struct-level: top-of-block comment; field-level: per-field trailing comment / future HDF5 attribute) | ✔ ok |
[[h5::name("disk_name")]] | field | Renames one field on disk; overrides any struct-level name_all convention | ✔ ok |
[[h5::name_all("snake_case" \| "camelCase" \| "PascalCase" \| "kebab-case")]] | struct | Applies a naming convention to every field's on-disk name uniformly | ✔ ok |
[[h5::ignore]] | field | Omit the field from persistence entirely; reads zero-initialize it. Use for runtime-only state (cached handles, debug counters) | ✔ ok |
[[h5::chunk(N)]] | struct | Default chunk size for the Tier-2 streaming dataset (each scatter call appends one row) | ✔ ok |
[[h5::compress(filter, level)]] | struct | Compression filter on the Tier-2 dataset's DCPL (e.g. compress(gzip, 9)) | ✔ ok |
[[h5::on_missing(value)]] | field | Default value substituted when the field is absent on read (e.g. older schema version lacked the field) | ✔ ok |
[[h5::serialize_full]] | field | Force a non-POD field (e.g. std::vector<T>) to be inlined into the compound type via VLEN serialisation, rather than skipped under Tier-1 emission | ✔ ok |
The attributes above are exactly what current h5cpp-compiler (v1.12.6) recognises in its fixtures and h5_attr_reader. [[h5::alias(...)]] and [[h5::version(...)]] are not currently implemented — schema-version + symbol-aliasing semantics are on the roadmap but the parser does not consume them today. Per-field [[h5::name("...")]] overrides any struct-level [[h5::name_all(...)]]. Combinations of the supported attributes compose cleanly: [[h5::name_all("snake_case"), h5::chunk(64), h5::compress(gzip, 6)]] on a struct is valid.
Tier-1 vs. Tier-2 emission
The compiler classifies each registered struct by inspecting its fields:
| If the struct contains… | Tier emitted | What generated.h looks like |
| Only trivially-copyable / standard-layout fields (POD) | Tier-1 | register_struct<T>() body with H5Tinsert per field. The dispatch uses dt_t<T> directly and writes/reads via the linear-value path. |
At least one non-POD field (std::string, std::vector<T>, etc.) | Tier-2 | scatter<T>(fd, path, ref) + gather<T>(fd, path, ref) specialisations. Each non-POD field is serialised through its dedicated vlen mechanism (vlen string, hvl_t, …). User code calls h5::write(fd, path, ref) / h5::read(fd, path, ref) and the gateway routes through scatter/gather. |
Mixed POD + non-POD but [[h5::serialize_full]] present | Tier-1 with silent skip | Non-POD fields are dropped from the compound. Useful when the POD slice is the canonical on-disk shape and the non-POD fields are scratch. |
18. C++26 reflection — pending
C++26 (P2996 Reflection for C++26 + P3394 Annotations for Reflection) makes the h5cpp-compiler tool optional: the same field enumeration and attribute reading move into the language as std::meta::* + [[=value]] annotation syntax.
| Mechanism | Today (C++17 / clang-based tool) | Under C++26 reflection |
| Field enumeration | h5cpp-compiler AST walks the struct | template for (constexpr auto m : std::meta::nonstatic_data_members_of(^T)) at compile time |
| Field type extraction | AST clang::FieldDecl::getType() | std::meta::type_of(m) |
| Field offset | offsetof(T, name) emitted into generated.h | std::meta::offset_of(m) |
| Field name | AST getName() emitted as "name" literal | std::meta::name_of(m) |
| Annotation value | h5cpp-compiler scans [[h5::name("...")]] from the AST | std::meta::annotations_of(m, ^h5::name) |
| Annotation syntax | [[h5::name("label")]] float tag; | [[=h5::name{"label"}]] float tag; (P3394 — the = marks an annotation expression) |
| Code emission | H5CPP_REGISTER_STRUCT(T) body written to generated.h, included after <h5cpp/all> | dt_t<T> and scatter<T> specialisations synthesised inline at the point of use; no generated.h file needed |
| Build step | CMake GENERATED line invokes the h5cpp compiler before C++ compilation | Single C++ compilation pass; the compiler is the C++ compiler |
The attribute vocabulary stays identical across both worlds — the migration is at the implementation layer, not the user-facing surface. A struct annotated for the C++17 tool also works under C++26 reflection once the library lands the consteval dt_t<T> generators.
| Status of each piece | Today | Notes |
[[h5::*]] attribute parser (clang tool) | ✔ ok — production today | examples/reflection/ and every generated.h in tree |
std::meta::* field walker | ◇ pending | Library has no C++26 dispatch path yet; awaits P2996 acceptance and a stable compiler implementation |
[[=h5::name{"..."}]] P3394 annotation reader | ◇ pending | Depends on P3394 (annotations) advancing — currently in WG21 Evolution review |
consteval dt_t<T> synthesis (compile-time H5Tinsert recording) | ◇ pending | Will replace the register_struct<T>() body emission once the standard reflection API is usable |
| Single-step C++26 build (no external compiler tool) | ◇ pending | The endpoint — once the above three land, examples/reflection/ builds with zero external tooling |
| Backwards-compatibility shim | ◇ pending | Same [[h5::*]] attributes must keep working through the transition; the C++17 tool stays as the bootstrap path while early C++26 implementations are unstable |
When the C++26 path lands, the rows in §15 (Tier-2 scatter/gather) get a parallel "synthesised inline at use" alternative; nothing in this map changes semantically, only where the code lives. This section will be re-evaluated once a tier-1 compiler implementation (GCC 16+ / Clang 22+ / MSVC vNext) ships with usable P2996 + P3394 support.