Template Alchemy: Mastering Variadic Packs with TypePack (Part 1 of 8)
Part 1: The Anatomy of Type Containers

Search for a command to run...
Part 1: The Anatomy of Type Containers

No comments yet. Be the first to comment.
If you have a Linux server at home or in a small office, chances are you also have Windows machines that need to access files on it. The traditional path — installing and configuring Samba by hand — i

Part 8: Set Semantics — Existence, Indexing, and Uniqueness

Part 7: Flattening — Collapsing Nested TypePacks

Part 6: Transformation - Removing or Replacing a Specific Type

Part 5: Refining the Pack — Deletion and Range Removal In the previous chapters, we focused on building and expanding our TypePack. However, effective type manipulation often requires surgical precision in removing unwanted elements. Whether you're s...

In modern C++, variadic templates allow us to work with an arbitrary number of template arguments. However, anyone who has delved deep into Template Metaprogramming (TMP) knows that "parameter packs" (the Ts... in a template) are quite elusive. They aren't objects, they aren't types, and they aren't quite arrays. They are a language construct that can only be expanded in specific contexts.
To harness the power of these packs, we need a way to "capture" them, move them around, and manipulate them without immediate expansion. This is where TypePack comes in.
A pack of types is not a first-class citizen in C++. You cannot store a pack in a variable, nor can you create a using alias for a raw pack.
Consider the following illegal code:
template <typename... Ts>
struct Error {
using MyPack = Ts...; // ERROR: A parameter pack cannot be used as an alias
};
Because of this limitation, if you want to pass a collection of types to another metafunction or store them for later use, you must wrap them in a container.
std::tuple ExampleThe most common example of a pack container in the Standard Library is std::tuple.
// std::tuple "captures" the pack Ts into a single type
std::tuple<int, double, char> myData;
While std::tuple is great for storing values of different types, it carries overhead because it’s a concrete class designed for runtime use. For pure compile-time metaprogramming, we need something lighter—a "type-only" container.
TypePackTypePack is a minimalist structure designed to hold a pack of types. It inherits from std::type_identity to make it easier to refer to the pack's own type in complex transformations.
#include <type_traits>
template <class... Ts>
struct TypePack : std::type_identity<TypePack<Ts...>> {
// This structure intentionally has no data members.
// Its only purpose is to carry the pack 'Ts...' in its signature.
};
By wrapping Ts... inside TypePack, the pack becomes part of a single, concrete type. You can now pass this TypePack to other templates, nest it, or return it from a metafunction.
To ensure our TypePack behaves as expected, we can use static_assert. These tests verify that different instances of TypePack are recognized as unique types and that they correctly inherit their own identity.
// 1. Verify that empty packs are valid
using EmptyPack = TypePack<>;
static_assert(!std::is_same_v<EmptyPack, TypePack<int>>);
// 2. Verify identity inheritance
using MyTypes = TypePack<int, float, double>;
static_assert(std::is_same_v<MyTypes::type, MyTypes>);
// 3. Nested TypePacks (TypePacks can hold other TypePacks)
using Nested = TypePack<TypePack<int, int>, char>;
static_assert(std::is_same_v<Nested::type, TypePack<TypePack<int, int>, char>>);
TypePack is the foundation of our library. By wrapping a variadic pack into a struct, we bypass the language limitations that prevent us from treating packs as entities. We have moved from a raw, "unstable" pack to a stable, nameable type.
In the next part, we will explore how to calculate the size of a TypePack and how to get individual types from it.