Map a set of types to unique IDs and runtime reinterpret back from ID and pointer in C++17

I wanted to create a relatively universal way of serialising an object, by doing a memcpy and generating a unique type ID. Stored together they can be used, for example, by another thread to restore a copy of the object for further processing.

This is used, for example, where one tread has a logging function requiring minimal overhead and converting the object to its logged state is considerably more expensive than making a raw copy.

Some other requirements / design choices:

  • IDs should be dense (no gaps)
  • IDs should fit the smallest unsigned possible
  • No RTTI allowed
  • A slight convenience overhead at the runtime restoring side is acceptable (and present in the form of the generated ‘if’ tree to match an ID to a type)
  • Both sides have access to the definition of the mapping
  • Handling of constructors with side-effects is left up to the user

Id’d love to hear any critiques or possible pitfalls! Below is the header pasted into a silly example to show the idea and interface:

// This keeps a variable in the final output: #define KEEP(x) volatile auto x __attribute__((unused))  #include <type_traits> #include <cstdint> #include <cstddef>  namespace type_list {         /**      * @brief Extract the N-th type of a set of template arguments      *      * @tparam N    Index of type to extract      * @tparam Ts   Arguments      */     template <std::size_t N, typename T, typename... Ts>     struct nth_type {         using type = typename nth_type<N-1, Ts...>::type;     };      template <typename T, typename... Ts>     struct nth_type<0, T, Ts...> {         using type = T;     };      /**      * @brief Extract the N-th type of a set of template arguments      *      * @tparam N    Index of type to extract      * @tparam Ts   Arguments      */     template <std::size_t N, typename... Ts>     using nth_type_t = typename nth_type<N, Ts...>::type;      /**      * @brief Find the index of the first matching type `IN` in a set of types.      *      * @tparam IN   Type to find      * @tparam T    First of type list      * @tparam Ts   Rest of type list      */     template <typename IN, typename T, typename... Ts>     struct index_of_type {         static_assert(sizeof...(Ts) != 0 || std::is_same_v<IN, T>, "No index for type found");         static constexpr const std::size_t value { 1 + index_of_type<IN, Ts...>::value };     };      template <typename IN, typename... Ts>     struct index_of_type<IN, IN, Ts...> {         static constexpr const std::size_t value { 0 };     };      /**      * @brief Find the index of the first matching type `IN` in a set of types.      *      * @tparam IN   Type to find      * @tparam Ts   Type list      */     template <typename IN, typename... Ts>     static constexpr const auto index_of_type_v { index_of_type<IN, Ts...>::value };      namespace {         static constexpr void noop(const std::size_t = 0) {}          template <size_t I, typename... Ts>         struct map_visit_impl {             template <typename F, typename E>             static constexpr decltype(auto) visit(const std::size_t id, const void *const ptr, F func, E on_error) {                 if (id == I - 1) {                     return func(*reinterpret_cast<const nth_type_t<I-1, Ts...> *const>(ptr));                 } else {                     return map_visit_impl<I - 1, Ts...>::visit(id, ptr, func, on_error);                 }             }              template <typename F, typename E>             static constexpr decltype(auto) visit(const std::size_t id, void *const ptr, F func, E on_error) {                 if (id == I - 1) {                     return func(*reinterpret_cast<nth_type_t<I-1, Ts...> *const>(ptr));                 } else {                     return map_visit_impl<I - 1, Ts...>::visit(id, ptr, func, on_error);                 }             }         };          template <typename... Ts>         struct map_visit_impl<0, Ts...> {             template <typename F, typename E>             static constexpr void visit(const std::size_t id, const void *const, F func, E on_error) {                 // If arrived here we have a invalid id                 on_error(id);             }              template <typename F, typename E>             static constexpr void visit(const std::size_t id, void *const, F func, E on_error) {                 // If arrived here we have a invalid id                 on_error(id);             }         };     }      /**      * @brief Create an ID map of a set of types.      *      * @tparam Ts Type list      */     template <typename... Ts>     struct map {         /**          * @brief Get the type with index `N`          *          * @tparam N Index of type to get          */         template <std::size_t N>         using type = type_list::nth_type_t<N, Ts...>;          /**          * @brief The ID number (index) of a given type `T`          *          * @tparam T          */         template <typename T>         static constexpr const std::size_t id { type_list::index_of_type_v<T, Ts...> };          /**          * @brief Number of types stored          */         static constexpr const std::size_t size { sizeof...(Ts) };          /**          * @brief Convert any given pointer to the type matching `id` and pass          * it to a function `func` as only argument using a `reinterpret_cast`.          *          * @tparam F    Function type          * @param id    id / index of type          * @param ptr   Storage location          * @param func  Handler function          * @return      Result of handler function          */         template <typename F, typename E = decltype(noop)>         static constexpr decltype(auto) parse(const std::size_t id, const void *const ptr, F func, E on_error = noop) {             return map_visit_impl<sizeof...(Ts), Ts...>::visit(id, ptr, func, on_error);         }          /**          * @brief Convert any given pointer to the type matching `id` and pass          * it to a function `func` as only argument using a `reinterpret_cast`.          *          * @tparam F    Function type          * @param id    id / index of type          * @param ptr   Storage location          * @param func  Handler function          * @return      Result of handler function          */         template <typename F, typename E = decltype(noop)>         static constexpr decltype(auto) parse(const std::size_t id, void *const ptr, F func, E on_error = noop) {             return map_visit_impl<sizeof...(Ts), Ts...>::visit(id, ptr, func, on_error);         }     }; }  // Generate unique types template <size_t N> struct c {};  // Demo set of types using map = type_list::map<     uint8_t, uint16_t, uint32_t, int8_t, int16_t, int32_t,     c<__COUNTER__>, c<__COUNTER__>, c<__COUNTER__>, c<__COUNTER__>,     c<__COUNTER__>, c<__COUNTER__>, c<__COUNTER__>, c<__COUNTER__>,     c<__COUNTER__>, c<__COUNTER__>, c<__COUNTER__>, c<__COUNTER__>,     c<__COUNTER__>, c<__COUNTER__>, c<__COUNTER__>, c<__COUNTER__>,     c<__COUNTER__>, c<__COUNTER__>, c<__COUNTER__>, c<__COUNTER__>,     c<__COUNTER__>, c<__COUNTER__>, c<__COUNTER__>, c<__COUNTER__>,     c<__COUNTER__>, c<__COUNTER__>, c<__COUNTER__>, c<__COUNTER__>,     c<__COUNTER__>, c<__COUNTER__>, c<__COUNTER__>, c<__COUNTER__>,     c<__COUNTER__>, c<__COUNTER__>, c<__COUNTER__>, c<__COUNTER__>,     c<__COUNTER__>, c<__COUNTER__>, c<__COUNTER__>, c<__COUNTER__> >;  // Just a quick hack to initialise some test data: char bytes[] = "Test string, bla bla";  uint64_t counter = 0;  void fn(const std::size_t n, const std::size_t i) {     __asm volatile("# LLVM-MCA-BEGIN type_map_overhead");     map::parse(n, &bytes[i], [&](auto& val) {         __asm volatile("# LLVM-MCA-END");         // Needed because the handler needs to apply to any type in the map:         if constexpr (std::is_integral_v<decltype(val)>) {             counter += val;         }     }); }  int main() {     KEEP(k1) = map::id<uint16_t>; // size_t => 1     KEEP(k2) = std::is_same_v<map::type<1>, uint16_t>; // bool => true      fn(0, 0);     fn(1, 1);     fn(2, 2);      KEEP(k4) = counter; } 

YA hash tuple in C++17

Is there something terribly wrong with this implementation?

template <class T> constexpr inline std::size_t hash_combine(T const& v,   std::size_t const seed = {}) noexcept {   return seed ^ (std::hash<T>()(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2)); }  template <typename ...T> struct hash<std::tuple<T...>> {   template <typename A1, typename ...A, std::size_t ...I>   static auto apply_tuple(std::tuple<A1, A...> const& t,     std::index_sequence<I...>) noexcept   {     if constexpr(sizeof...(A))     {       return hash_combine(std::get<0>(t),         std::hash<std::tuple<A const&...>>()(           std::tuple<A const&...>{std::get<I + 1>(t)...}         )       );     }     else     {       return std::hash<std::remove_cv_t<std::remove_reference_t<A1>>>()(         std::get<0>(t));     }   }    auto operator()(std::tuple<T...> const& t) const noexcept   {     return apply_tuple(t,       std::make_index_sequence<sizeof...(T) - 1>()     );   } };