C++ Compile-Time Type Map

Introduction

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.

specialized_compile_time_type_map.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <cstdint>
#include <type_traits>

template <typename KeyType, typename... KeyTypes>
constexpr bool has_duplicated_keys()
{
// C++ 17 fold expression.
return (
std::is_same<typename KeyType::key, typename KeyTypes::key>::value ||
...);
}

template <typename QueryType, typename... KeyTypes>
constexpr bool has_key()
{
return has_duplicated_keys<QueryType, KeyTypes...>();
}

template <typename KeyType, typename ValueType>
struct TypePair
{
using key = KeyType;
using value = ValueType;
};

template <typename QueryType>
struct SpecializedTypeMap
{
// Perform the following specialized type mapping.
// K0 -> V0
// K1 -> V1
// K2 -> V2

using K0 = int16_t;
using K1 = int32_t;
using K2 = int64_t;

using V0 = uint16_t;
using V1 = uint32_t;
using V2 = uint64_t;

// Perform the type mapping.
using type_map_0 = TypePair<K0, V0>;
using type_map_1 = TypePair<K1, V1>;
using type_map_2 = TypePair<K2, V2>;

static_assert(!has_duplicated_keys<type_map_0, type_map_1, type_map_2>(),
"Duplicate key values.");
static_assert(has_key<TypePair<QueryType, QueryType>, type_map_0,
type_map_1, type_map_2>(),
"Key not found.");

// 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>>;
};

int main()
{
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.

1
2
$ g++ specialized_compile_time_type_map.cpp -o specialized_compile_time_type_map --std c++17
$ ./specialized_compile_time_type_map

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.

generic_compile_time_type_map.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include <cstdint>
#include <tuple>
#include <type_traits>

template <typename TypePair, typename... TypePairs>
constexpr bool has_duplicated_keys()
{
// C++ 17 fold expression.
return (
std::is_same<typename TypePair::key, typename TypePairs::key>::value ||
...);
}

template <typename KeyType, typename ValueType>
struct TypePair
{
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>
struct TypeMap : 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 = typename decltype(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>;

int main()
{
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.

1
2
$ g++ generic_compile_time_type_map.cpp -o generic_compile_time_type_map --std c++17
$ ./generic_compile_time_type_map

The implementation used C++ 17 features such as fold expressions, pack expansion in using-declaration, and std::is_same_v. Those features are not available in C++ 14 but may have workarounds. However, we will not discuss these in this article.

References

Author

Lei Mao

Posted on

12-22-2024

Updated on

12-22-2025

Licensed under


Comments