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);
}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);
}