|
H5CPP
v1.14.0
Modern C++ templates for HDF5 serial and parallel I/O
|
|
This example walks the HDF5 datatype-customization surface. The point is simple: HDF5 has more type primitives than the obvious native scalars, and every one of them is reachable through a single macro in h5cpp.
Four arguments:
| Arg | What |
|---|---|
TYPE | C++ type to register |
NAME | String literal printed by h5::name<TYPE>::value |
CREATE_EXPR | Expression returning an HDF5 hid_t (H5Tcopy, H5Tcreate, H5Tenum_create) |
BODY | Brace-enclosed setup with handle in scope; use {} if nothing to do |
Drop that in, and h5::write(fd, path, v), h5::read<YourType>, and h5::read<std::vector<YourType>> all work as if YourType were a native scalar.
The macro expands to the underlying boilerplate (a dt_t<T> specialization + h5::name<T> spec). The raw form is shown at the bottom of this README under What the macro expands to — useful when debugging a registration or working in environments that disallow macros.
| File | Purpose |
|---|---|
datatypes.cpp | Six sections, one HDF5 datatype primitive per section |
custom_types.hpp | The four user-defined types + their dt_t<T> specializations |
README.md | This file |
The header pulls in <h5cpp/core> itself because the dt_t<T> specializations need dt_p<T> to inherit from. <h5cpp/all> provides the IO surface.
| HDF5 primitive | When you reach for it | h5cpp pattern |
|---|---|---|
H5Tcopy(H5T_NATIVE_*) | Strong-typedef a native scalar with a domain name | dt_t<Celsius> over float |
H5Tset_precision | Tighten a uchar/uint to N bits (paired with h5::nbit) | dt_t<n_bit> over uchar with precision 2 |
H5T_OPAQUE | Bytes that HDF5 should not interpret | dt_t<two_bit> 1-byte opaque blob |
H5Tenum_create + H5Tenum_insert | Named enumerated values, integer payload | dt_t<Status> over uint8_t with four entries |
| Native vendor types | std::float16_t (C++23), other small floats | already shipped in h5cpp/H5Tall.hpp |
Other primitives belong elsewhere in the example tree:
H5T_COMPOUND from a POD struct → examples/compound/H5T_STRING fixed and VLEN → examples/string/, examples/csv/examples/half-float/CelsiusA wrapper around float with the same memory layout but a distinct identity in C++ and a distinct name on disk:
No memory overhead, no runtime cost. h5dump shows the dataset's type as Celsius rather than float.
bitstring::n_bitn_bit is a uchar with H5Tset_precision(handle, 2) — the on-disk type carries only the bottom two bits per element. Combined with h5::nbit on the dcpl, the chunked dataset compresses out the unused bits:
Useful for low-cardinality categorical data, sample-rate-quantised audio, or anywhere you'd otherwise reach for bit-packing by hand.
bitstring::two_bitH5T_OPAQUE tells HDF5 the byte is uninterpreted. h5cpp passes it through. The tag is metadata for tools like h5dump:
two_bit packs four 2-bit fields into a single byte; h5cpp doesn't need to know about that. The wrapper exposes operator[] so user code can read out the four fields, but the on-disk representation is one uninterpreted byte.
h5::name<T>::value is the compile-time string. h5::dt_t<T> instances stream a runtime description of the HDF5 type they wrap — handy for logs and for verifying which spec the dispatch picked:
Specialize h5::name<T> once per custom type. The runtime introspection comes free from dt_t<T>'s stream insertion operator.
std::float16_t (C++23)Already shipped: h5cpp/H5Tall.hpp has a dt_t<std::float16_t> spec that emits a 16-bit IEEE binary16 layout. Gated on __STDCPP_FLOAT16_T__ (libstdc++ 13+, libc++ 18+):
When the toolchain lacks <stdfloat>, the example prints "skipped" with a pointer to examples/half-float/. That directory contains pre-C++23 demos using half_float::half (Christian Rau) and Imath::half (OpenEXR) — same dt_t<T> pattern, vendored third-party header.
Status → H5T_ENUMHDF5 has a native enumerated type. The integer payload round-trips through h5::write / h5::read; h5dump prints the names:
h5dump -p datatypes.h5 shows:
CMake target examples-datatypes. Built at C++23 so the std::float16_t section can compile; the half-float path is feature-gated on __STDCPP_FLOAT16_T__. No linalg dependencies — pure STL.
H5CPP_REGISTER_DATATYPE(Celsius, "Celsius", H5Tcopy(H5T_NATIVE_FLOAT), {}) expands to:
12 lines collapsed into 3. Useful to know when:
-E preprocessor outputusing first, or hand-roll the spec)The macro lives in h5cpp/H5Tall.hpp next to the existing H5CPP_REGISTER_STRUCT and H5CPP_REGISTER_TYPE macros — same family, same conventions.
One macro per type. The H5T* factory call captures what the type is on disk; the body captures how HDF5 should treat it. Same shape across all four custom types — only the third and fourth arguments change.
custom_types.hpp — rendered with syntax highlightingdatatypes.cpp — rendered with syntax highlighting