|
H5CPP
v1.14.0
Modern C++ templates for HDF5 serial and parallel I/O
|
|
This example shows the small set of moves needed to store C++ structs in HDF5. The point is simple: a user-defined struct becomes an HDF5 compound type, the compound type is reflected by the H5CPP compiler, and the same struct round-trips through h5::write / h5::read without anyone touching H5Tinsert.
Two tiers are covered:
register_struct<T>() specialization. Single H5Dwrite/H5Dread per call.std::string or std::vector<T> fields. The compiler emits h5::scatter<T> / h5::gather<T> specializations that walk the live object and serialise the variable-length parts.| File | Purpose |
|---|---|
compound.cpp | Tier-1 + Tier-2 examples — create, write, read, scatter/gather |
pod.h | Tier-1 POD struct declarations (sn::example::record_t and friends) |
non-pod.h | Tier-2 struct with VLEN fields (sn::sensor::timeseries_t) |
generated.h | H5CPP-compiler output: register_struct<T> + scatter<T> / gather<T> |
<h5cpp/all> pulls in everything h5cpp needs. The compiler-emitted generated.h carries the compound descriptors and the scatter/gather bodies for the structs h5cpp saw in this TU; it follows the h5cpp includes.
The on-disk layout follows the C++ layout. Nested namespaces, typedefs, fixed-size C arrays, and nested structs are all handled:
h5::pod<T>{} | h5::take(n) is the same generator pipe used elsewhere in the examples (from H5Uall.hpp). It hands back std::vector<T> of default-constructed records — handy for round-trip tests.
Property-list fragments do not care about order. Both of these are equivalent:
The dispatch parses by argument type at compile time. No runtime ordering cost.
POD structs are written field-for-field. Structs with std::string or std::vector<T> cannot be — the variable-length fields are pointers into separately-allocated storage. The H5CPP compiler handles this by emitting scatter/gather specializations that walk the live object.
The C++ attributes drive the on-disk shape:
| Attribute | Effect |
|---|---|
[[h5::doc("...")]] | Set on the struct's HDF5 documentation |
[[h5::chunk(128)]] | Default chunk shape for datasets of this struct |
[[h5::compress("gzip", 6)]] | Default filter chain for datasets of this struct |
[[h5::name("label")]] | Rename a field on the HDF5 side |
[[h5::ignore]] | Skip this field — not persisted |
For tier-2 structs you call the compiler-generated entry points directly:
The declarations live in h5cpp; the bodies are emitted by the H5CPP compiler into generated.h based on what it sees in non-pod.h. If you change the struct, regenerate generated.h.
Tier-1 uses the regular h5::write / h5::read API. Tier-2 uses h5::scatter / h5::gather because it has work to do per element that the generic path cannot.
The example is wired into CMake as examples-compound and depends on Armadillo (used by some of the compound generator helpers). Running it produces compound.h5 in the current directory.
User code writes a regular C++ struct. The H5CPP compiler reads it, decides which tier it belongs to, and emits the matching HDF5 type descriptors and serialisation bodies into generated.h. The dispatch path is then identical to writing any other C++ object.
compound.cpp — rendered with syntax highlightinggenerated.h — rendered with syntax highlightingnon-pod.h — rendered with syntax highlightingpod.h — rendered with syntax highlighting