|
H5CPP
v1.14.0
Modern C++ templates for HDF5 serial and parallel I/O
|
|
A header-only, feature-detection-based set of stream inserters that lets you write
for any value whose surface area matches a familiar STL container — std::vector, std::list, std::map, std::stack, std::queue, std::priority_queue, std::pair, std::tuple, custom containers exposing .begin()/.end(), and arbitrary compositions of all of the above.
Lives in h5cpp/H5Uall.hpp, automatically pulled by <h5cpp/all> and <h5cpp/core>. No opt-in macro required.
Inspired by the HDF Group mailing-list post automated-printing-2022-04-07, modernised onto C++17 detection idioms and the prototype branch's h5::meta traits.
| Category | Detector | Output shape | Example types |
|---|---|---|---|
| Iterable | has_iterator<T> && !is_string<T> && !is_tuple<T> | [a,b,c,...] | vector, list, deque, forward_list, array, set, multiset, unordered_set, unordered_multiset, map, multimap, unordered_map, unordered_multimap |
| Stack adaptor | has_top && has_pop && has_empty && !has_iterator | [top,…,bottom] (destructive copy) | std::stack, std::priority_queue |
| Queue adaptor | has_front && has_pop && has_empty && !has_iterator | [front,…,back] (destructive copy) | std::queue |
**std::pair<K,V>** | direct overload | {key:value} | std::pair, std::map::value_type |
**std::tuple<Ts...>** | direct overload | <v0,v1,…,vN> | std::tuple |
Long containers truncate at **H5CPP_CONSOLE_WIDTH** (default 10) with a trailing , ....
The selection is feature detection — Walter Brown's WG21 N4436 idiom. Each overload is gated on which member functions a type exposes, not what it inherits from. The pretty-printer never asks "is this `std::stack`?"; it asks "does this thing have `.top()`, `.pop()`, `.empty()`, and not `.begin()`?".
Four new detectors live in h5::meta:
joining the pre-existing has_iterator<T>, has_size<T>, has_data<T>, is_string<T>, is_tuple<T>. The five inserters are global-namespace function templates so ADL picks them up for STL types.
is_string<T> excluded from the iterable overload — std::cout << std::string("hi") continues to use the standard library's char-stream operator.is_tuple<T> excluded from the iterable overload even though C++20 makes tuples iterable in some sense..begin()/.end() routes through the iterable overload; the stack/queue overloads require !has_iterator. A custom container exposing both would print as an iterable.map::value_type is pair<const K, V> — the iterable overload's *it dereferences to a pair, which prints via overload (4): [{k1:v1},{k2:v2}, ...].stack/queue/priority_queue have no iterators, so the inserter makes a local copy and drains it via top/pop or front/pop. The caller's container is untouched.Recursion happens implicitly via the os << *it call — if *it is itself an iterable (vector<vector<int>>), a pair (map::value_type), or a tuple, the appropriate overload is selected for the element.
The original container is never modified — we copy and drain.
Same shape as 4.2 but using .front() and .pop().
std::pair<K,V>The key and value print via their operator<<, so pair<int, vector<double>> becomes {42:[1.0,2.0,3.0]}.
std::tuple<Ts...>Comma fold over the parameter pack; each fields element streams through its own operator<<.
Note: stack is LIFO so its top (most recently pushed) comes first. Priority queue is ordered by std::less by default — largest first.
examples-container run)The three flat containers (vector, deque, list) show identical data — same input, different on-disk dispatch paths, invisible at the user level. vec_array4 demonstrates recursion: outer brackets are the vector, inner brackets are each std::array<int,4>.
Containers longer than H5CPP_CONSOLE_WIDTH elements truncate with a trailing , .... The default is 10. Override before including h5cpp:
H5CPP_CONSOLE_WIDTH is #ifndef-guarded — the first definition wins. The macro lives at file scope in H5Uall.hpp. Setting it inside a translation unit is local to that TU.
The implementation counts iteration steps after the first element:
So H5CPP_CONSOLE_WIDTH = 10 prints up to 11 elements before the cutoff: the first one, then 10 more from the while body. A 12-element container shows the first 11 followed by , .... If your terminal is wider than that, bump the macro.
| Location | Contents |
|---|---|
h5cpp/H5Uall.hpp | H5CPP_CONSOLE_WIDTH macro, four new detectors (has_top, has_front, has_pop, has_empty), five operator<< overloads |
h5cpp/H5cout.hpp | h5cpp-specific stream inserters for dxpl_t, sp_t, dt_t<T>, impl::array<T>, pipeline_t<T>. No STL inserter here — historical vector<T>-only one was removed (it shadowed the new generic and didn't recurse) |
h5cpp/H5meta.hpp | Pre-existing detectors (has_iterator, has_size, has_data, …) used by the new inserters |
examples/container/container.cpp | Tier-1 demo: uses the pretty-printer for HDF5 round-trip diagnostics |
examples/pprint/pprint.cpp | Standalone demo of the full inserter surface — no HDF5 IO involved |
The detection rules will pick up the most generic overload that matches. A container that has both .begin()/.end() and .top()/.pop() (rare) prints as an iterable, not a stack — the iterable overload's SFINAE includes has_iterator<T> and the stack overload's includes !has_iterator<T>, so they're mutually exclusive by design.
operator<<If *it is a user-defined type without its own operator<<, the iterable overload's os << *it becomes a hard compile error. The fix is to provide the user type's own operator<< — the pretty-printer recurses into whatever streamers exist; it does not synthesise them.
std::string and std::string_viewBoth pass has_iterator but are excluded from the iterable overload via is_string<T>. They stream as "hello" (just the characters) via the standard library, not as [h,e,l,l,o]. That's intentional.
The pair overload calls os << p.first << ":" << p.second. If K (e.g., a custom struct) has no operator<<, the build fails when the map is streamed. Same fix as 8.2.
Stack and queue adaptors print by copying the container and draining the copy. For a million-element std::priority_queue<std::string>, this allocates a million strings worth of intermediate state. Pretty-printing is for diagnostic use — not a hot path.
The iterable overload calls std::begin()/stdend() and walks the range. If another thread mutates the container during the stream, behaviour is undefined — same as iterating any STL container concurrently.
To make a custom container pretty-print:
.top()/.front(), .pop(), .empty() and the corresponding overload kicks in.operator<< — the pretty-printer's design does not require you to extend it; just provide the streamer for your type and existing iterable/pair/tuple inserters will recurse through it.You don't need to extend the h5cpp headers to add a new printable type. The mechanism is open by default.
Prior to this change, h5cpp/H5cout.hpp had a std::vector<T>-specific operator<< (line 113, pre-edit). It was more specialised than the new generic iterable, so the compiler preferred it. But its body did os << vec[i] directly without recursing into the element's pretty-printer — meaning vector<array<int,4>>, vector<pair<K,V>>, and any other vector-of-non-scalar would emit "no known conversion" errors during template instantiation.
The legacy overload has been removed from H5cout.hpp (replaced with a doc comment). The new generic iterable in H5Uall.hpp handles std::vector<T> exactly the same way it handles every other iterable — and recurses correctly. Existing user code unaffected; the output is byte-identical for flat vectors.
h5cpp/H5Uall.hppadds five global-namespaceoperator<<overloads — iterable, stack adaptor, queue adaptor, pair, tuple — selected by feature detection. Together they pretty-print any STL-shaped value, including arbitrarily nested compositions, to a single line truncated atH5CPP_CONSOLE_WIDTH(default 10). No opt-in macro, no extension API; works on any user type that provides the matching member-function surface.