From C++11, std::tuple is an incredible expansion to Modern C++, that offers a fixed-size collection of heterogeneous values. Unfortunately, tuples can be somewhat dubious to manage in a conventional fashion. But, subsequently released C++ standard introduced a few features & helpers that greatly reduce the necessary boilerplate. 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.
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
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)
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;
}
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");
template <typename... Args>
void print(Args... args) {
(void(cout << args << endl), ...);
}
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
}
// 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
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);
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;
}
}
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.
Previously published at http://www.vishalchovatiya.com/variadic-template-cpp-implementing-unsophisticated-tuple/