Implementing your own version of std::tuple

C++
Author

dev::author

Published

August 23, 2025

Basic std::tuple design

A tuple is an arbitrary collection of heterogenous data. It is a recursive data-structure. A tuple has a static component and a run-time component. The list of types are baked into the tuple definition at compile-time. The actual values/objects held by the tuple can be objects known at run-time.

We will design a tuple class template. Implementing your own version tuple type is a good exercise to flex your metaprogramming muscles.

#include <utility>
#include <memory>
#include <format>
#include <iostream>
#include <cassert>

namespace dev 
{
    // TypeList definition
    template<typename... Ts>
    struct TypeList{
        using type = TypeList<Ts...>;
        static constexpr auto value = TypeList<Ts...>{};
    };

    // TypeList Indexing 
    template<size_t I, typename List>
    struct nth_element;

    template<typename First, typename... Rest>
    struct nth_element<1,TypeList<First, Rest...>>{
        using type = First;
    };

    template<size_t N, typename First, typename... Rest>
    struct nth_element<N, TypeList<First, Rest...>> : nth_element<N-1, TypeList<Rest...>>{};

    // Implement tuple. This is a forward declaration.
    template <typename... Types>
    class tuple;

    // Base case
    template <>
    class tuple<> { };

    template<typename Head, typename... Tail>
    requires std::is_trivially_default_constructible_v<Head>
    class tuple<Head, Tail...>{
        using type = TypeList<Head, Tail...>;

        private:
        Head m_head;
        tuple<Tail...> m_tail;

        public:
        tuple()
        : m_head{}
        , m_tail{}
        {}

        tuple(Head head, Tail... tail)
        : m_head{head}
        , m_tail{tail...}
        {}

        Head head() const{
            return m_head;
        }

        tuple<Tail...> tail() const{
            return m_tail;
        }

        constexpr auto empty(){
            return std::tuple<>();
        }

        constexpr auto initialize(Head head, Tail... tail){
            m_head = head;
            if constexpr(sizeof...(Tail))
                m_tail.initialize(tail...);
        }
    };

    // Implement get
    template <unsigned N, typename... Types>
    auto get(const tuple<Types...>& tuple) {
        if constexpr(N == 0)
            return tuple.head();
        else
            return get<N-1>(tuple.tail());
    }
}
int main(){
    dev::tuple<int> tup;
    assert(get<0>(tup) == 0);
}

Compiler Explorer

Tuple Algorithms

Implementing transform for a std::tuple{t1,t2,...,tn}

template<typename TupleT, typename Func, size_t... Is>
constexpr auto transform_impl(TupleT tup, Func func, std::index_sequence<Is...> indexes){
    return std::make_tuple(func(std::get<Is>(tup))...);
}

// transform
template<typename TupleT, typename Fn>
constexpr auto transform(Fn func, TupleT tup)
{
    constexpr auto index_seq = std::make_index_sequence<std::tuple_size_v<TupleT>>{};
    return transform_impl(tup, func, index_seq);
}

Implementing select_tuple for a std::tuple{t1,...,tn}

// select_tuple
template<typename TupleT, size_t... Is>
constexpr auto select_tuple(TupleT tuple, std::index_sequence<Is...> idx_sequence)
{
    return std::make_tuple((std::get<Is>(tuple))...);
}

Reversing a tuple

template<typename TupleT, size_t... Is>
constexpr auto reverse_tuple_impl(TupleT tuple, std::index_sequence<Is...> idx_seq){
    constexpr auto rev_idx_seq = seq_reverse<std::index_sequence<Is...>>::value;
    return select_tuple(tuple, rev_idx_seq);
}

template<typename TupleT>
constexpr auto reverse_tuple(TupleT tuple){
    constexpr std::index_sequence idx_sequence = std::make_index_sequence<std::tuple_size_v<TupleT>>{};
    return reverse_tuple_impl(tuple, idx_sequence);
}

Implementing tuple concatenation

template<typename TupleT1, typename TupleT2, size_t... I1s, size_t... I2s>
constexpr auto cat_tuple_impl(TupleT1 tuple1, TupleT2 tuple2, std::index_sequence<I1s...> seq1, std::index_sequence<I2s...> seq2){
    return std::make_tuple(std::get<I1s>(tuple1)...,std::get<I2s>(tuple2)...);
}

template<typename TupleT1, typename TupleT2>
constexpr auto cat_tuple(TupleT1 t1, TupleT2 t2)
{
    constexpr std::index_sequence seq1 = std::make_index_sequence<std::tuple_size_v<TupleT1>>{};
    constexpr std::index_sequence seq2 = std::make_index_sequence<std::tuple_size_v<TupleT2>>{};
    return cat_tuple_impl(t1, t2, seq1, seq2);
}

Implementing zip for a pair of tuples

template<typename TupleT1, typename TupleT2, size_t... I1s, size_t... I2s>
constexpr auto zip_tuple_impl(TupleT1 tuple1, TupleT2 tuple2, std::index_sequence<I1s...> seq1, std::index_sequence<I2s...> seq2){
    return std::make_tuple(std::make_tuple(std::get<I1s>(tuple1), std::get<I2s>(tuple2))...);
}

template<typename TupleT1, typename TupleT2>
constexpr auto zip_tuple(TupleT1 t1, TupleT2 t2){
    constexpr std::index_sequence seq1 = std::make_index_sequence<std::tuple_size_v<TupleT1>>{};
    constexpr std::index_sequence seq2 = std::make_index_sequence<std::tuple_size_v<TupleT2>>{};
    return zip_tuple_impl(t1, t2, seq1, seq2);
}

Implementing filter for a tuple