[C++] Getting Started With Typelists
Find this example here on compiler explorer.
Since quite a while I started diving deeper into templates and typelists. I googled a lot and I highly recommend C++ Templates: The Complete Guide.
For me it was and still is hard to find practical examples to understand certain details. Which is the reason I'll start from scratch and try to get one to better understand templates, typelists and metaprogramming.
To keep things simple, a typelist is just a list of different types. No values, just types. This can be done with variadic templates like this:
template<typename... Types>
struct typelist{};
using short_bool_string_type = typelist<short, bool, std::string>;
This means our typelist is just an empty struct which accepts any number of types. Then we can define our type short_bool_string_type
which represents now the three types short
, bool
and string
.
Pretty simple but we can't really do much with it by now. Let's create some functions to manipulate typelists.
Before we start...
I will use the following style guide in this and the following articles:
- structs/classes are lower case, like
struct foo {};
- template parameters are upper case, like
template<typename Type>
- type alias are upper case, like
using Type = int
- ending
_t
of a class/struct will represent a type - ending
_v
of a class/struct will represent a value (probably not in this series)
In most cases when we use algorithms with templates and typelists, you will find the template definition like: template<typename Head, typename... Tail>
. This can be sometimes confusing and to start with, consider the following function to print all types from a typelist:
template<typename Head, typename... Tail>
void print_types(typelist<Head,Tail...>& t)
{
std::cout << typeid(Head).name() << '\n';
typelist<Tail...> remaining_types;
print_types(remaining_types);
}
template<typename Head>
void print_types(typelist<Head>& t)
{
std::cout << typeid(Head).name() << '\n';
}
By doing so, we'll get the first type in our list in Head
and the rest in Tail...
. We can then create another list without Head
and call print_types
recursively until only one type is left. Find this example here on compiler explorer.
And this helped me a lot to understand the basic idea behind typelist algorithms, so let's get started with manipulating a typelist.
Accessing And Manipulating Front
Like other containers (std::vector
, std::array
, etc.) we can access the first element with front()
and insert new elements. The same we'll do now with the typelist. We create an alias front
which gets the first type of our typelist.
template<typename... Types>
class typelist{};
using short_bool_string_type = typelist<short, bool, std::string>;
// class template for the first element
template <typename List>
struct front;
// partial specialization for the front type
template<typename Head, typename... Tail>
struct front<typelist<Head, Tail...>>
{
using Type = Head;
};
// front_t alias which accesses the front type from the given list
template<typename List>
using front_t = typename front<List>::Type;
// if the first time is not short we'll get a compile error
static_assert(std::is_same<short, front_t<short_bool_string_type>>());
This is all pretty static, let's create push_front
and pop_front
to manipulate a typelist.
// class template for pop front
template<typename List>
struct pop_front;
// partial specialization for pop front
template<typename Head, typename... Tail>
struct pop_front<typelist<Head, Tail...>>
{
using Type = typelist<Tail...>; // we create a new typelist without Head
};
// pop_front_t alias to access the type of pop_front
template<typename List>
using pop_front_t = typename pop_front<List>::Type;
// the type short_bool_string_type without short leaves a typelist with bool and string
static_assert(
std::is_same<
typelist<bool, std::string>,
pop_front_t<short_bool_string_type>>()
);
And also the implementation for push front:
// class template for push front
template<typename Typelist, typename Element>
struct push_front;
// partial specialization for push front
template<typename... Typelist, typename Element>
struct push_front<typelist<Typelist...>, Element>
{
using Type = typelist<Element, Typelist...>;
};
// push_front_t alias to access the type of push_front
template<typename Typelist, typename Element>
using push_front_t = typename push_front<Typelist, Element>::Type;
// the type short_bool_string_type with a beginning int leaves a list with int short bool string
static_assert(
std::is_same<
typelist<int, short, bool, std::string>,
push_front_t<short_bool_string_type, int>>()
);
Appending Types With push_back
Finally we are implementing a push_back_t
alias to append types to an existing typelist:
// class template for push back
template<typename Typelist, typename Element>
struct push_back;
// partial specialization for push back
template<typename... Typelist, typename Element>
struct push_back<typelist<Typelist...>, Element>
{
using Type = typelist<Typelist..., Element>;
};
// push_back_t alias to access the type of push_back
template<typename Typelist, typename Element>
using push_back_t = typename push_back<Typelist, Element>::Type;
// the type short_bool_string_type with a trailing int is a list with short bool string int
static_assert(
std::is_same<
typelist<short, bool, std::string, int>,
push_back_t<short_bool_string_type, int>>()
);
Conclusion
This is the beginning of my typelist articles. I know this is at the beginning pretty abstract without practical examples but we'll get to it after covering the basics.
In the next articles I continue with some more typelist functions and then we'll get to tuples
. And ultimately I'll try to create a practical example where we use the code from here.
You can find this example here on compiler explorer.
But that's it for now.
Best Thomas