From C++11, std::tuple is an incredible expansion to Modern C++, that offers a fixed-size col­lec­tion of het­ero­ge­neous values. Un­for­tu­nately, tu­ples can be somewhat dubious to manage in a conventional fash­ion. But, subsequently released C++ stan­dard in­tro­duced a few fea­tures & helpers that greatly re­duce the nec­es­sary boil­er­plate. So, in this article, I will explain the variadic template in C++ with the help of unsophisticated tuple
implementation. And also walks you through a tricky part of tuple i.e.
loop through tuple element. In spite of the fact that I have shrouded the variadic template in my prior article i.e. C++ Template: A Quick UpToDate Look. So, my focus here would be a blend of variadic template & tuple implementation with more up to date C++ gauges.

Motivation

Variadic Class Template: Implementing Tuple Class

template <typename... T>
struct Tuple { };
template<
            typename T, 
            typename... Rest    // Template parameter pack
        >
struct Tuple<T, Rest...> {      // Class parameter pack
    T first;
    Tuple<Rest...> rest;        // Parameter pack expansion

    Tuple(const T& f, const Rest& ... r)
        : first(f)
        , rest(r...) {
    }
};

Tuple<bool>                 t1(false);           // Case 1
Tuple<int, char, string>    t2(1, 'a', "ABC");   // Case 2

How Does Variadic Class Template Works?

To understand variadic class template, consider use case 2 above i.e.
Tuple<int, char, string>   t2(1, 'a', "ABC");
You can visualize this as follows:
Tuple<int, char, string>
-> int first
-> Tuple<char, string> rest
    -> char first
    -> Tuple<string> rest
        -> string first
        -> Tuple<> rest
            -> (empty)

Variadic Function Template: Implementing get<>() Function for Tuple Class

template<
            size_t idx, 
            template <typename...> class Tuple, 
            typename... Args
        >
auto get(Tuple<Args...> &t) {
    return GetHelper<idx, Tuple<Args...>>::get(t);
}
template<
            size_t idx, 
            typename T
        >
struct GetHelper;
template<
            typename T, 
            typename... Rest
        >
struct GetHelper<0, Tuple<T, Rest...>> {
    static T get(Tuple<T, Rest...> &data) {
        return data.first;
    }
};
template<
            size_t idx, 
            typename T, 
            typename... Rest
        >
struct GetHelper<idx, Tuple<T, Rest...>> {
    static auto get(Tuple<T, Rest...> &data) {
        return GetHelper<idx - 1, Tuple<Rest...>>::get(data.rest);
    }
};
So that’s it! Here is the whole functioning code, with some example use in the main function:
// Forward Declaration & Base Case -----------------------------------------
template<
            size_t idx,
            typename T
        >
struct GetHelper { };

template <typename... T>
struct Tuple { };
// -------------------------------------------------------------------------

// GetHelper ---------------------------------------------------------------
template<
            typename T,
            typename... Rest
        >
struct GetHelper<0, Tuple<T, Rest...>> { // Specialization for index 0
    static T get(Tuple<T, Rest...> &data) {
        return data.first;
    }
};

template<
            size_t idx,
            typename T,
            typename... Rest
        >
struct GetHelper<idx, Tuple<T, Rest...>> { // GetHelper Implementation
    static auto get(Tuple<T, Rest...> &data) {
        return GetHelper<idx - 1, Tuple<Rest...>>::get(data.rest);
    }
};
// -------------------------------------------------------------------------

// Tuple Implementation ----------------------------------------------------
template<
            typename T,
            typename... Rest
        >
struct Tuple<T, Rest...> {
    T                   first;
    Tuple<Rest...>      rest;

    Tuple(const T &f, const Rest &... r)
        : first(f)
        , rest(r...) {
    }
};
// -------------------------------------------------------------------------


// get Implementation ------------------------------------------------------
template<
            size_t idx, 
            template <typename...> class Tuple, 
            typename... Args
        >
auto get(Tuple<Args...> &t) {
    return GetHelper<idx, Tuple<Args...>>::get(t);
}
// -------------------------------------------------------------------------


int main() {
    Tuple<int, char, string> t(500, 'a', "ABC");
    cout << get<1>(t) << endl;
    return 0;
}

Variadic Template vs Fold Expression

Processing a Parameter Pack With Recursion

void print() {}
template<   
            typename First, 
            typename... Rest                    // Template parameter pack
        >     
void print(First first, Rest... rest) {         // Function parameter pack
    cout << first << endl;
    print(rest...);                             // Parameter pack expansion
} 
print(500, 'a', "ABC");

Processing a Parameter Pack With Fold Expression

template <typename... Args>
void print(Args... args) {
    (void(cout << args << endl), ...);
}

Loop-Through/Iterate Over Tuple Elements in C++

template <typename... Args>
void print(const std::tuple<Args...> &t) {
    for (const auto &elem : t) // Error: no begin/end iterator
        cout << elem << endl;
}
template <typename... Args>
void print(const std::tuple<Args...>&   t) {
    for (int i = 0; i < sizeof...(Args); ++i)
        cout << std::get<i>(t) << endl;    // Error :( , `i` needs to be compile time constant
}

C++11: Loop Through Tuple Elements

// Template recursion
template <size_t i, typename... Args>
struct printer  {
    static void print(const tuple<Args...> &t) {
        cout << get<i>(t) << endl;
        printer<i + 1, Args...>::print(t);
    }
};

// Terminating template specialisation
template <typename... Args>
struct printer<sizeof...(Args), Args...> {
    static void print(const tuple<Args...> &) {}
};

template <typename... Args>
void print(const tuple<Args...> &t) {
    printer<0, Args...>::print(t);
}

tuple<int, char, string> t(1, 'A', "ABC");
print(t);
// Note: might not work in GCC, I've used clang

C++17: Loop Through Tuple Elements

template <typename... Args>
void print(const std::tuple<Args...> &t) {
    std::apply([](const auto &... args) {
        ((cout << args << endl), ...);
    }, t);
}
template <class... Ts>
struct overloaded : Ts... {
    using Ts::operator()...;
};

// Deduction guide, google `CTAD for aggregates` for more info
template <class... Ts>
overloaded(Ts...) -> overloaded<Ts...>;   // not needed from C++20

auto f = overloaded {
    [](const int &a)        { cout << "From int: " << a << endl; },
    [](const char &b)       { cout << "From char: " << b << endl; },
    [](const string &c)     { cout << "From string: " << c << endl; },
};

tuple<int, char, string>    t(1, 'A', "ABC");
std::apply([&](const auto &... e) { (f(e), ...); }, t);

C++23: Loop Through Tuple Elements

template <typename... Args>
void print(const std::tuple<Args...> &t) {
    for... (const auto &elem : t)
        cout << elem << endl;
}
template <typename... Args>
void print(const tuple<Args...> &t) {
    {
        const auto &elem = get<0>(t);
        cout << elem << endl;
    }
    {
        const auto &elem = get<1>(t);
        cout << elem << endl;
    }
    {
        const auto &elem = get<2>(t);
        cout << elem << endl;
    }
}

Closing Words

There are still many things missing in our tuple class like copy constructor, move constructors, some operators and helper classes(like std::tuple_size). But I hope now you get the idea of how it can be implemented using the
variadic template. By the way, implementing those missing things will be a good start for learning variadic template on your own.