# 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.

```cpp
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):

```cpp
// 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.

```cpp
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.
