Templatized Mapping of HDF5 Native Types in Modern C++
The Problem (Posted by Mark C. Miller, Feb 5, 2018)
Mark needed a simple way to map std::vector<T>
to the correct H5T_NATIVE_*
type in a templated dataset writer:
template <class T>
static void WriteVecToHDF5(hid_t fid, const char* name,
const std::vector<T>& vec, int d2size)
{
hsize_t dims[2] = {vec.size() / d2size, d2size};
hid_t spid = H5Screate_simple(d2size > 1 ? 2 : 1, dims, nullptr);
hid_t dsid = H5Dcreate(fid, name, HDF5Type<T>().Type(), spid,
H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
H5Dwrite(dsid, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, &vec[0]);
H5Dclose(dsid);
H5Sclose(spid);
}
````
He implemented a hierarchy using `HDF5Type<T>()`, specialized per type to return the corresponding native constant (`H5T_NATIVE_INT`, `H5T_NATIVE_FLOAT`, etc.).
But... inheritance, virtuals, and boilerplate made him wonder: *Is there a cleaner way?*
## The H5CPP Way: Keep It Simple and Modern (Steven Varga)
Steven Varga, author of **H5CPP**, weighed in with a minimalist approach inspired by modern C++ design—ditch virtual dispatch, avoid inheritance, and leverage function overloads and template inference.
### ✅ Solution
```cpp
inline hid_t h5type(const char&) { return H5T_NATIVE_CHAR; }
inline hid_t h5type(const signed char&) { return H5T_NATIVE_SCHAR; }
inline hid_t h5type(const unsigned char&) { return H5T_NATIVE_UCHAR; }
inline hid_t h5type(const short&) { return H5T_NATIVE_SHORT; }
inline hid_t h5type(const unsigned short&) { return H5T_NATIVE_USHORT; }
inline hid_t h5type(const int&) { return H5T_NATIVE_INT; }
inline hid_t h5type(const unsigned int&) { return H5T_NATIVE_UINT; }
inline hid_t h5type(const long&) { return H5T_NATIVE_LONG; }
inline hid_t h5type(const unsigned long&) { return H5T_NATIVE_ULONG; }
inline hid_t h5type(const long long&) { return H5T_NATIVE_LLONG; }
inline hid_t h5type(const unsigned long long&) { return H5T_NATIVE_ULLONG; }
inline hid_t h5type(const float&) { return H5T_NATIVE_FLOAT; }
inline hid_t h5type(const double&) { return H5T_NATIVE_DOUBLE; }
inline hid_t h5type(const long double&) { return H5T_NATIVE_LDOUBLE; }
template<typename T>
inline hid_t h5type() { return h5type(T()); }
✅ Usage
Or, more directly:
Benefits of This Pattern
Feature | Virtual Class Approach | Overload-Based Approach |
---|---|---|
Compile-time resolution | ❌ | ✅ |
Easy to extend for new types | ⚠️ (needs specialization) | ✅ (just another overload) |
Runtime dispatch | ✅ | ❌ |
Readability | ⚠️ verbose | ✅ concise |
Performance (no virtual calls) | ❌ | ✅ |
Conclusion
The overload-based mapping pattern presented by Steven Varga in response to the HDF forum thread reflects a clean, idiomatic modern C++ style:
- Clear separation of logic
- No need for inheritance boilerplate
- Efficient and extensible
If you're designing a lightweight HDF5 backend—or building C++ infrastructure like H5CPP—this pattern is robust and production-ready.