HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppModerate

Very basic tuple implementation

Submitted by: @import:stackexchange-codereview··
0
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:

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]:

// 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.