Skip to content

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++ designditch 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

hid_t type_id = h5type<T>();

Or, more directly:

hid_t dsid = H5Dcreate(fid, name, h5type<T>(), spid,
                       H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);

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.