|
H5CPP
v1.14.0
Modern C++ templates for HDF5 serial and parallel I/O
|
|
h5cpp's managed handle types — every wrapper catalogued in Handles, Descriptors & Property Lists (the object handles h5::fd_t / h5::ds_t / h5::gr_t / h5::at_t / h5::ob_t / h5::sp_t, the datatype template h5::dt_t<T>, the reference handle h5::reference_t, and every property-list wrapper h5::fapl_t / h5::fcpl_t / h5::dapl_t / …) — wrap HDF5's raw hid_t integer ID in an RAII container that owns its lifetime. The conversion policy is the contract for how those wrappers and the raw hid_t interconvert at call sites that mix h5cpp and the HDF5 C API.
Three orthogonal axes, each toggleable at compile time:
| Axis | Default | Opt-out macro | Effect when disabled |
|---|---|---|---|
to-CAPI (h5::fd_t → hid_t) | enabled (H5CPP_CONVERSION_TO_CAPI) | -DH5CPP_CONVERSION_TO_CAPI_DISABLED | conversion operator removed; passing a wrapper to the C API does not compile |
from-CAPI (hid_t → h5::fd_t) | enabled (H5CPP_CONVERSION_FROM_CAPI) | -DH5CPP_CONVERSION_FROM_CAPI_DISABLED | constructor removed; cannot wrap a raw hid_t returned by HDF5 |
| implicit vs explicit (cast required?) | implicit (H5CPP_CONVERSION_IMPLICIT) | -DH5CPP_CONVERSION_EXPLICIT | static_cast<hid_t>(fd) required at every call site that needs the raw handle |
The implementation lives in h5cpp/H5config.hpp lines 53-64 (the three #ifndef / #define blocks that flip the macros) and h5cpp/H5Iall.hpp lines 16-20 (the H5CPP__EXPLICIT selector that the wrapper template uses to qualify the conversion operator).
With no opt-out macros set — the default in 95% of consumer code — both directions are wired and the conversion is implicit:
The wrapper is binary-compatible with hid_t (a single hid_t handle; member), so the call-site code is identical to using the raw integer ID — there is no per-call overhead.
Compile with -DH5CPP_CONVERSION_EXPLICIT to require an explicit cast at every conversion site. Useful when migrating a large codebase from raw hid_t to h5cpp wrappers and you want the compiler to flag every mixed call:
Under the hood: H5CPP__EXPLICIT expands to explicit instead of empty (see H5Iall.hpp:16-20), qualifying both the from-CAPI constructor and the to-CAPI operator hid_t().
The conversion operators are non-owning views onto the same hid_t. The wrapper retains exclusive ownership; the conversion does NOT transfer responsibility:
| Operation | HDF5 refcount | Why |
|---|---|---|
From-CAPI ctor: h5::fd_t fd(raw) | incremented (via H5Iinc_ref) | The raw hid_t keeps its existing owner. The wrapper takes a second reference so the destructor can independently call capi_close without double-freeing. |
To-CAPI conversion: H5Dcreate(fd, ...) | unchanged | The wrapper retains ownership. The C API call sees the same hid_t value; when it returns, the wrapper still holds the only ref, and the destructor will close it. |
Brace-init ctor: h5::fd_t{raw} | unchanged | Distinct from the from-CAPI ctor — used when the wrapper is taking ownership of a raw handle that h5cpp itself produced (e.g., the return value of H5Fcreate). No inc_ref because there's no existing C-side owner. |
| Copy ctor / copy-assign | incremented | Both copies refer to the same underlying HDF5 object; each independently closes on destruction. |
| Move ctor / move-assign | unchanged; source invalidated | Source handle set to H5I_UNINIT; destructor on the source is a no-op (H5Iis_valid check). |
| Destructor | decremented (via capi_close) | HDF5's close functions are refcount-aware: H5Fclose etc. decrement the ref and only release the underlying resource at refcount 0. |
The "**DON'T**" pattern this protects against:
(The full call-site discussion lives in h5cpp/H5Fcreate.hpp:14-49 — the docstring on h5::create walks through both the DO and DON'T forms.)
h5cpp's async surface (h5::async::create, h5::async::open, h5::async::* factories) returns specialised wrappers that deliberately disable both conversion directions:
The template parameters false, false (from_capi, to_capi) select a specialisation where operator hid_t() const = delete; is declared at the type level (see H5Iall.hpp:155). Any attempt to pass an async descriptor to a raw HDF5 call fails to compile with a clear "use of
deleted function" diagnostic:
The intent: an async descriptor's value is only valid on the executor thread; passing it to a synchronous C API call would bypass the executor and access the handle from the wrong thread. The type-level deletion makes that class of bug impossible.
Internal code that legitimately needs the raw hid_t from an async wrapper reads handle directly (the protected: field is exposed to the executor implementation via friendship); user code goes through the typed h5::write / h5::read / h5::async::* overloads that detect the type via is_async_v<T> and dispatch through the FAPL-resolved executor.
The full list of async-mode wrappers — h5::async::fd_t, h5::async::ds_t, h5::async::gr_t, h5::async::at_t, h5::async::ob_t — is in Handles, Descriptors & Property Lists §Async-mode handles".
@section conversion_inventory Which handles use which policy
The <tt>h5::impl::detail::hid_t</tt> template's 3rd and 4th parameters
(<tt>from_capi</tt>, <tt>to_capi</tt>) select the conversion policy. Across every
public alias h5cpp ships there are only two combinations:
<table class="markdownTable">
<tr><th>Policy</th><th>Template parameters</th><th>Covers</th></tr>
<tr><td><b>Default — both directions enabled</b><br/>
(governed by <tt>H5CPP__EXPLICIT</tt> as documented in
@ref conversion_default and @ref conversion_explicit)</td>
<td><tt>hid_t\<…, true, true, hdf5_class\></tt></td>
<td>Every object handle (<tt>h5::fd_t</tt>, <tt>h5::ds_t</tt>, <tt>h5::gr_t</tt>,
<tt>h5::at_t</tt>, <tt>h5::ob_t</tt>, <tt>h5::sp_t</tt>), the datatype template
<tt>h5::dt_t\<T\></tt>, the reference handle <tt>h5::reference_t</tt>, and
every property-list wrapper (<tt>h5::fapl_t</tt>, <tt>h5::fcpl_t</tt>,
<tt>h5::dapl_t</tt>, <tt>h5::dcpl_t</tt>, <tt>h5::dxpl_t</tt>, <tt>h5::lcpl_t</tt>,
<tt>h5::lapl_t</tt>, <tt>h5::gcpl_t</tt>, <tt>h5::gapl_t</tt>, <tt>h5::tcpl_t</tt>,
<tt>h5::tapl_t</tt>, <tt>h5::acpl_t</tt>, <tt>h5::ocpl_t</tt>, <tt>h5::ocrl_t</tt>,
<tt>h5::fmpl_t</tt>, <tt>h5::scpl_t</tt>).</td></tr>
<tr><td><b>Async — both directions DELETED at type level</b><br/>
(see @ref conversion_async)</td>
<td><tt>hid_t\<…, false, false, hdf5_class\></tt></td>
<td><tt>h5::async::fd_t</tt>, <tt>h5::async::ds_t</tt>, <tt>h5::async::gr_t</tt>,
<tt>h5::async::at_t</tt>, <tt>h5::async::ob_t</tt></td></tr>
</table>
For the complete reference — every alias, the HDF5 close function
it calls, its typical factory, plus the shape-descriptor types and
the property-list defaults — see
@ref link_handle_reference "Handles, Descriptors & Property Lists".
The 5th template parameter (<tt>hdf5_class</tt>) selects a specialisation
that adds class-specific members orthogonal to the conversion
policy (e.g. the <tt>attribute</tt> specialisation carries the parent
dataset's <tt>hid_t</tt> for <tt>ds[name]</tt> subscript access). It does not
affect the conversion direction — see
@ref handle_ref_implementation for the full parameter breakdown.
@section conversion_use_cases When to override the defaults
Three concrete scenarios for compiling with one of the opt-out
macros:
<table class="markdownTable">
Goal
Macro
Trade-off
Audit a large codebase for raw hid_t usage during a migration to h5cpp wrappers
-DH5CPP_CONVERSION_EXPLICIT
Every mixed call site now needs static_cast<hid_t>(...); the compiler flags any that don't have one. Once the audit is done, drop the macro to restore implicit conversions.
Forbid mixing h5cpp wrappers with the C API entirely (h5cpp-only codebase)
-DH5CPP_CONVERSION_TO_CAPI_DISABLED (with H5CPP_CONVERSION_EXPLICIT)
The wrapper has no operator hid_t at all; passing a wrapper to a C API call fails to compile. Useful for enforcing a strict h5cpp surface.
Forbid wrapping raw hid_t values that originate outside h5cpp (e.g. third-party C bindings)
-DH5CPP_CONVERSION_FROM_CAPI_DISABLED
The wrapper has no from-CAPI constructor; only h5cpp factories can produce wrappers. Useful when integrating with a foreign HDF5 layer that has its own lifetime tracking.