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:
Prefix: We take the first
Indexelements.Suffix: We skip everything from the start up to the end of the unwanted range (
Index + Size).Join: We concatenate the prefix and the suffix, effectively "stitching" the pack back together over the hole.
We use
requiresclauses andstatic_assertto 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.



