Skip to main content

Command Palette

Search for a command to run...

Template Alchemy: Mastering Variadic Packs with TypePack (Part 2 of 8)

Part 2: Measuring and Indexing the Pack

Published
3 min read
Template Alchemy: Mastering Variadic Packs with TypePack (Part 2 of 8)

In the first part of this series, we introduced the TypePack as a container to "capture" variadic template arguments. However, a container is only useful if we can inspect its contents. In this article, we will implement two fundamental operations: querying the number of types in a pack and retrieving a specific type by its index.

The Implementation: size and element_t

To make TypePack useful, we need to provide a way to access its metadata at compile-time. We achieve this by adding a static constexpr size and a template alias for indexing.

template <class... Ts>
struct TypePack : std::type_identity<TypePack<Ts...>> {
    // 1. Determine the number of elements in the pack
    static constexpr size_t size = sizeof...(Ts);

    // 2. Access a specific type by index
    template <size_t Index>
    using element_t = details::TypePackElement<Index, TypePack>::type;
};

1. The size Property

C++ provides the sizeof... operator specifically for parameter packs. It returns a size_t representing the number of types in the pack. By baking this into the TypePack struct, we allow users to check the pack's length without needing to expand it themselves.

2. The element_t Logic (Recursive Traversal)

Accessing an element at a specific index is more complex. Unlike a runtime array, you cannot use Ts[i]. Instead, we must use recursive template specialization to "peel off" types from the front of the pack until we reach the desired index.

Here is the supporting logic (usually placed in a details namespace):

// Primary template
template <size_t Index, class Pack>
struct TypePackElement;

// Base Case 1: Error handling for empty packs or out-of-bounds
template <size_t Index>
struct TypePackElement<Index, TypePack<>> {
    static_assert(AlwaysFalse<std::integral_constant<size_t, Index>>, "Index out of range");
};

// Base Case 2: Found the type (Index is 0)
template <class T, class... Ts>
struct TypePackElement<0, TypePack<T, Ts...>> : std::type_identity<T> {
};

// Recursive Step: Reduce the index and move to the next type
template <size_t Index, class T, class... Ts>
requires (Index > 0 && Index <= sizeof...(Ts))
struct TypePackElement<Index, TypePack<T, Ts...>> : TypePackElement<Index - 1, TypePack<Ts...>> {
};
  • How it works: If we ask for index 2 of <int, long, double>, the compiler matches the recursive step. It then looks for index 1 of <long, double>, and finally index 0 of <double>. At index 0, the specialization extracts double as the type.

Validation with static_assert

We use static_assert within our tests to prove that our indexing logic is accurate and that the size constant correctly reflects the pack's length.

TEST(TypePackTests, ElementType)
{
    using Pack = TypePack<int, long, double, char>;

    // Verify the size
    static_assert(Pack::size == 4);

    // Verify self-identity
    static_assert(std::is_same_v<Pack::type, Pack>);

    // Verify individual element access
    static_assert(std::is_same_v<Pack::element_t<0>, int>);
    static_assert(std::is_same_v<Pack::element_t<1>, long>);
    static_assert(std::is_same_v<Pack::element_t<2>, double>);
    static_assert(std::is_same_v<Pack::element_t<3>, char>);
}

Conclusion

By implementing size and element_t, we have turned a raw pack into a structured list that we can navigate. These tools are the building blocks for more advanced operations like searching, filtering, and transforming packs.

In the next part, we will look at how to extract subsets of our TypePack: retrieving the first N elements, the trailing elements, or a specific range of types.

Template Alchemy: Mastering Variadic Packs with TypePack (Part 2 of 8)