In C++ template programming, sometimes I would like to declare a variable of certain type based on a query type. This requires creating a compile-time type map that maps the key type to the value type.
In this article, I will show how to implement a compile-time type map in C++ using C++ 17 features.
C++ Specialized Compile-Time Type Map
It’s possible to create specialized compile-time type maps using std::conditional_t and std::is_same_v. But it is often inconvenient if there are many different type mappings in the map or there are many such maps to be defined.
In the following example, we define a specialized compile-time type map that maps the key type to the value type. More specifically, we define a type map that maps int16_t to uint16_t, int32_t to uint32_t, and int64_t to uint64_t.
// Find type using std::conditional_t. using find_type = std::conditional_t< std::is_same_v<QueryType, K0>, V0, std::conditional_t<std::is_same_v<QueryType, K1>, V1, V2>>; };
intmain() { static_assert( std::is_same_v<SpecializedTypeMap<int16_t>::find_type, uint16_t>); static_assert( std::is_same_v<SpecializedTypeMap<int32_t>::find_type, uint32_t>); static_assert( std::is_same_v<SpecializedTypeMap<int64_t>::find_type, uint64_t>); // Unable to compile because the key does not exist in the type map. // static_assert( // std::is_same_v<SpecializedTypeMap<int8_t>::find_type, uint16_t>); }
To build and run the program, please run the following commands.
We could see that if there are many different type mappings in the map, find_type will become very long and hard to maintain. In addition, the code in the SpecializedTypeMap class is not reusable.
C++ Generic Compile-Time Type Map
To create a generic compile-time type map, we created the following implementation. The generic compile-time type map can be used for creating multiple maps conveniently, similar to a std::map value map that is used at runtime.
template <typename KeyType, typename ValueType> structTypePair { using key = KeyType; using value = ValueType;
// Is does not matter what exactly the type is, as long as the type contains // the key type. The following also works. // static TypePair get_pair_instance(std::tuple<KeyType, KeyType>) static TypePair get_pair_instance(std::is_same<KeyType, KeyType>) { // This can always be constructed. return TypePair{}; } };
template <typename... TypePairs> structTypeMap : public TypePairs... { using TypePairs::get_pair_instance...;
static_assert(!has_duplicated_keys<TypePairs...>(), "Compile time type map got duplicate key values.");
template <typename QueryType> using find_type = typenamedecltype(get_pair_instance( std::is_same<QueryType, QueryType>{}))::value; };
// Define compile-time type maps. using TypePair0 = TypePair<int16_t, uint16_t>; using TypePair1 = TypePair<int32_t, uint32_t>; using TypePair2 = TypePair<int64_t, uint64_t>; using TypePair3 = TypePair<int64_t, uint64_t>;
using TypeMapIntToUInt0 = TypeMap<TypePair0>; using TypeMapIntToUInt1 = TypeMap<TypePair0, TypePair1>; using TypeMapIntToUInt2 = TypeMap<TypePair0, TypePair1, TypePair2>; // Unable to compile because of the has_duplicated_keys static_assert. // TypePair2 and TypePair3 have the same key type. // using TypeMapIntToUInt3 = TypeMap<TypePair0, TypePair1, TypePair2, // TypePair3>;
intmain() { static_assert( std::is_same_v<TypeMapIntToUInt0::find_type<int16_t>, uint16_t>); static_assert( std::is_same_v<TypeMapIntToUInt1::find_type<int16_t>, uint16_t>); static_assert( std::is_same_v<TypeMapIntToUInt1::find_type<int32_t>, uint32_t>); static_assert( std::is_same_v<TypeMapIntToUInt2::find_type<int16_t>, uint16_t>); static_assert( std::is_same_v<TypeMapIntToUInt2::find_type<int32_t>, uint32_t>); static_assert( std::is_same_v<TypeMapIntToUInt2::find_type<int64_t>, uint64_t>); // Unable to compile because the key does not exist in the type map. // static_assert( // std::is_same<TypeMapIntToUInt2::find_type<int8_t>, uint64_t>::value, // ""); }
To build and run the program, please run the following commands.