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

A non-recursive tuple_element<index, Tuple> implementation

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
implementationnontuple_elementrecursivetupleindex

Problem

This is basically a non-recursive std::tuple_element implementation.

Note

To make this non-recursive, you must replace std::make_index_sequence with a non-recursive implementation. I left it with std::make_index_sequence in order to reduce the amount of unrelated code.

How it works

deduct has a specialization of deduct_impl that is generated from the index sequence template argument it receives. It is used in order to deduce the type at index in a variadic type template or tuple. It uses the itp_base and itp types.

itp and itp is an index-type-pair used to expand the variadic indices template with a type variadic template in order to match the generated specialization.

deducer puts it all together by specializing deduct and deduct_impl by using std::conditional_t to generate the correct specialization.

Basically, for std::tuple, in order to get the type at index 1, it creates itp_base, itp, itp_base and passes it to deduct and deduct_impl.

Source code

#include 
#include 

template 
struct itp_base {};

template 
struct itp : itp_base {};

template 
struct deduct;

template 
struct deduct>
{
    template 
    struct deduct_impl;

    template 
    struct deduct_impl..., itp, R...>>
    {
        using type = T;
    };
};

template 
class deducer
{
private:
    static_assert( index 
    struct deducer_impl;

    template 
    struct deducer_impl>
    {
        using type = typename deduct
        >::template deduct_impl
        , itp>::value,
                    itp,
                    itp_base
                >...
            >
        >::type;
    };

public:
    using type = typename deducer_impl>::type;
};


Convenience aliases

template 
struct tuple_element;

template 
struct tuple_element> : deducer {};

template 
using tuple_element_t = typename tuple_element::type;


Test case

```
#include
#include

int main()
{
using tuple_t = std::tuple;
static_assert( std::is_same, std::string>::value, "!

Solution

Your approach on finding the nth element relies on being able to construct a tuple with the first n-1 types being some predictable thing that you can match against. But the types you chose for std::tuple were, for index 1, std::tuple, itp, itp_base>.

But we don't actually need to introduce this itp thing to do this, we can simply turn it into std::tuple. That way, deduct_impl's specialization instead of taking a std::tuple..., itp, R...>, we can simplify to:

template 
using make_void = void;

template 
struct deduct_impl..., T, R...>>
{
    using type = T;
};


And on the other side, we pass in:

std::tuple...>


Same idea, just more direct.

For that matter, we don't even need the tuple! std::tuple is an expensive type to construct at compile-time, so it's best to avoid it entirely. Just pass in the types as a pack (though the following doesn't compile on gcc - though even there you don't need tuple, just use a light wrapper like template struct typelist;)

template 
struct deduct_impl;

template 
using make_void = void;

template 
struct deduct_impl..., T, R...>
{
    using type = T;
};


and:

using type = typename deduct
>::template deduct_impl
...
>::type;


A different approach

Another non-recursive way to do this would be through an inheritance tree. We basically take std::tuple and turn it into a type that inherits from indexed, indexed, and indexed:

template 
struct indexed {
    using type = T;
};

template 
struct indexer;

template 
struct indexer, Ts...>
: indexed...
{};


Then, given a function template constrained on an index, we can pull out the correct type:

template 
struct at_index {
private:
    template 
    static indexed select(indexed);

    using impl = indexer, Ts...>;
public:
    using type = typename decltype(select(impl{}))::type;
};


I don't know which solution is better, but this one is certainly shorter.

Code Snippets

template <std::size_t>
using make_void = void;

template <typename T, typename... R>
struct deduct_impl<std::tuple<make_void<indices>..., T, R...>>
{
    using type = T;
};
std::tuple<std::conditional_t<(indices == index), Types, void>...>
template <typename... Ts>
struct deduct_impl;

template <std::size_t>
using make_void = void;

template <typename T, typename... R>
struct deduct_impl<make_void<indices>..., T, R...>
{
    using type = T;
};
using type = typename deduct<index, std::make_index_sequence<index>
>::template deduct_impl
<
    std::conditional_t<(indices == index), Types, void>...
>::type;
template <std::size_t I, typename T>
struct indexed {
    using type = T;
};

template <typename Is, typename ...Ts>
struct indexer;

template <std::size_t ...Is, typename ...Ts>
struct indexer<std::index_sequence<Is...>, Ts...>
: indexed<Is, Ts>...
{};

Context

StackExchange Code Review Q#112547, answer score: 5

Revisions (0)

No revisions yet.