Skip to main content

Command Palette

Search for a command to run...

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

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

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 stripping specific traits or trimming a pack to fit a function signature, you need a reliable way to "cut" types out.

In this article, we will implement remove_at_t and the more general remove_range_t.

Streamlining the Interface

By now, you might notice a pattern: our complex operations are built by composing simpler ones. We add two new aliases to TypePack that leverage a unified removal logic.

template <class... Ts>
struct TypePack : std::type_identity<TypePack<Ts...>> {
    // ... previous definitions ...

    // Remove a single type at Index
    template <size_t Index>
    using remove_at_t = details::TypePackRemoveRange<Index, 1, TypePack>::type;

    // Remove a chunk of 'Size' types starting at Index
    template <size_t Index, size_t Size>
    using remove_range_t = details::TypePackRemoveRange<Index, Size, TypePack>::type;
};

The Surgical Logic: TypePackRemoveRange

The logic for removing a range is the conceptual inverse of the "Subpack" operation we wrote in Part 3. To remove a range, we don't keep the middle; we keep the ends.

How it works:

  1. Prefix: We take the first Index elements.

  2. Suffix: We skip everything from the start up to the end of the unwanted range (Index + Size).

  3. Join: We concatenate the prefix and the suffix, effectively "stitching" the pack back together over the hole.

  4. We use requires clauses and static_assert to ensure that we never attempt to remove elements outside the pack's boundaries.

template <size_t Index, size_t Size, class Pack>
struct TypePackRemoveRange;

template <size_t Index, size_t Size, class...Ts>
requires (Index > sizeof...(Ts) || Size > sizeof...(Ts) || Index + Size > sizeof...(Ts))
struct TypePackRemoveRange<Index, Size, TypePack<Ts...>> {
    static_assert(Index <= sizeof...(Ts), "Index out of range");
    static_assert(Size <= sizeof...(Ts), "Size out of range");
    static_assert(Index + Size <= sizeof...(Ts), "Index + Size out of range");
};

template <size_t Index, size_t Size, class...Ts>
requires (Index <= sizeof...(Ts) && Size <= sizeof...(Ts) && Index + Size <= sizeof...(Ts))
struct TypePackRemoveRange<Index, Size, TypePack<Ts...>> :
    TypePackConcat<typename TypePackFirstN<Index, TypePack<Ts...>>::type, typename TypePackSkipN<Index + Size, TypePack<Ts...>>::type> {
};

Validation with static_assert

Our tests cover both the removal of individual elements and various range sizes, ensuring the "stitching" logic is perfect.

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

    // Removing the first element
    static_assert(std::is_same_v<Pack::remove_at_t<0>, TypePack<long, double, char>>);

    // Removing a middle element
    static_assert(std::is_same_v<Pack::remove_at_t<2>, TypePack<int, long, char>>);
}

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

    // Removing a range of 0 elements leaves the pack unchanged
    static_assert(std::is_same_v<Pack::remove_range_t<1, 0>, Pack>);

    // Removing a range in the middle
    static_assert(std::is_same_v<Pack::remove_range_t<1, 2>, TypePack<int, char>>);

    // Removing everything
    static_assert(std::is_same_v<Pack::remove_range_t<0, 4>, TypePack<>>);
}

Conclusion

We have now completed the structural toolkit for our TypePack. We can create, measure, index, slice, grow, and shrink our type containers. This composition-based approach—where RemoveRange is simply a combination of FirstN, SkipN, and Concat—demonstrates the power of building a clean foundation.

In the next part, we will move beyond indices and ranges to work with types directly: we will learn how to remove all occurrences of a specific type and how to replace one type with another across the entire pack.