patterncppModerate
Very basic tuple implementation
Viewed 0 times
implementationtuplebasicvery
Problem
I've been messing with metaprogramming and variadic templates in C++, and I came up with this very primitive implementation of a tuple:
Being template metaprogramming, of course it looks terrible. Are there any ways I can clean it up? I've been picking up the nitty-gritty details of how templates work by messing around with stuff like this, but I'm sure I'm missing something that would simplify this.
constexpr bool GreaterThanZero(int N)
{
return N > 0;
}
template
struct Helper;
template
struct Helper
{
typedef typename Helper::type type;
};
template
struct Helper
{
typedef Head& type;
};
template
class TupleImpl;
template <>
class TupleImpl
{
};
template
class TupleImpl
{
protected:
Head head;
public:
template
Head& get()
{
static_assert(Depth == 0, "Requested member deeper than Tuple");
return head;
}
template
const Head& get() const
{
static_assert(Depth == 0, "Requested member deeper than Tuple");
return head;
}
};
template
class TupleImpl
{
protected:
Head head;
TupleImpl tail;
public:
template
typename std::enable_if::type get()
{
return head;
}
template
typename std::enable_if::type>::type get()
{
return tail.get();
}
template
typename std::enable_if::type get() const
{
return head;
}
template
typename std::enable_if::type>::type get() const
{
return tail.get();
}
};
template
class Tuple : public TupleImpl
{
public:
static constexpr std::size_t size()
{
return sizeof...(Elements);
}
};
int main()
{
using namespace std;
Tuple test;
Tuple<> test2;
test.get() = 1;
test.get() = 2;
cout () () << endl;
}Being template metaprogramming, of course it looks terrible. Are there any ways I can clean it up? I've been picking up the nitty-gritty details of how templates work by messing around with stuff like this, but I'm sure I'm missing something that would simplify this.
Solution
Below is how I would clean this up, or maybe partially re-write [live example]:
Comments:
-
With a bit more infrastructure (helper structs that are typically reused here and there) at the beginning, the main tuple implementation becomes just 20 lines.
-
Instead of a recursive implementation, I have switched to multiple (variadic) inheritance of single tuple elements
-
Now a specialization of
-
No specialization is needed for empty tuple. This definition includes empty tuple as a special case.
-
No static assertions needed, an out-of-range index will give an error anyway (but you can add for better messages of course).
Now, there are so many things missing. I would start from support for rvalue references and, of course, constructors. If you want to get an idea, you can have a look at my own tuple implementation (of which this one here is a miniature) including tuple views, expression templates for lazy evaluation, loops, algorithms, integration with all C++ operators, and much more.
Template metaprogramming does not have to look terrible!
// helpers
template
struct id { using type = T; };
template
using type_of = typename T::type;
template
struct sizes : id > { };
// choose N-th element in list
template
struct Choose;
template
struct Choose : Choose { };
template
struct Choose : id { };
template
using choose = type_of >;
// given L>=0, generate sequence
template >
struct Range;
template
struct Range > : Range > { };
template
struct Range > : sizes { };
template
using range = type_of >;
// single tuple element
template
class TupleElem
{
T elem;
public:
T& get() { return elem; }
const T& get() const { return elem; }
};
// tuple implementation
template
class TupleImpl;
template
class TupleImpl , T...> : TupleElem ...
{
template using pick = choose ;
template using elem = TupleElem >;
public:
template
pick & get() { return elem ::get(); }
template
const pick & get() const { return elem ::get(); }
};
template
struct Tuple : TupleImpl , T...>
{
static constexpr std::size_t size() { return sizeof...(T); }
};Comments:
-
With a bit more infrastructure (helper structs that are typically reused here and there) at the beginning, the main tuple implementation becomes just 20 lines.
-
Instead of a recursive implementation, I have switched to multiple (variadic) inheritance of single tuple elements
TupleElem, each with its own id N (so that each element is of unique type) and its own function get(). Hence Tuple's function get() just redirects to the appropriate base class.-
Now a specialization of
TupleElem for empty types can bring easily the desired empty base optimization without affecting the main tuple implementation.-
No specialization is needed for empty tuple. This definition includes empty tuple as a special case.
-
No static assertions needed, an out-of-range index will give an error anyway (but you can add for better messages of course).
Now, there are so many things missing. I would start from support for rvalue references and, of course, constructors. If you want to get an idea, you can have a look at my own tuple implementation (of which this one here is a miniature) including tuple views, expression templates for lazy evaluation, loops, algorithms, integration with all C++ operators, and much more.
Template metaprogramming does not have to look terrible!
Code Snippets
// helpers
template <typename T>
struct id { using type = T; };
template <typename T>
using type_of = typename T::type;
template <size_t... N>
struct sizes : id <sizes <N...> > { };
// choose N-th element in list <T...>
template <size_t N, typename... T>
struct Choose;
template <size_t N, typename H, typename... T>
struct Choose <N, H, T...> : Choose <N-1, T...> { };
template <typename H, typename... T>
struct Choose <0, H, T...> : id <H> { };
template <size_t N, typename... T>
using choose = type_of <Choose <N, T...> >;
// given L>=0, generate sequence <0, ..., L-1>
template <size_t L, size_t I = 0, typename S = sizes <> >
struct Range;
template <size_t L, size_t I, size_t... N>
struct Range <L, I, sizes <N...> > : Range <L, I+1, sizes <N..., I> > { };
template <size_t L, size_t... N>
struct Range <L, L, sizes <N...> > : sizes <N...> { };
template <size_t L>
using range = type_of <Range <L> >;
// single tuple element
template <size_t N, typename T>
class TupleElem
{
T elem;
public:
T& get() { return elem; }
const T& get() const { return elem; }
};
// tuple implementation
template <typename N, typename... T>
class TupleImpl;
template <size_t... N, typename... T>
class TupleImpl <sizes <N...>, T...> : TupleElem <N, T>...
{
template <size_t M> using pick = choose <M, T...>;
template <size_t M> using elem = TupleElem <M, pick <M> >;
public:
template <size_t M>
pick <M>& get() { return elem <M>::get(); }
template <size_t M>
const pick <M>& get() const { return elem <M>::get(); }
};
template <typename... T>
struct Tuple : TupleImpl <range <sizeof...(T)>, T...>
{
static constexpr std::size_t size() { return sizeof...(T); }
};Context
StackExchange Code Review Q#44546, answer score: 12
Revisions (0)
No revisions yet.