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<std::size_t>
and itp<std::size_t, T>
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<void, int, char>
, in order to get the type at index 1, it creates itp_base<0>, itp<1, int>, itp_base<2>
and passes it to deduct
and deduct_impl
.
Source code
#include <utility>
#include <tuple>
template <std::size_t index>
struct itp_base {};
template <std::size_t index, typename T>
struct itp : itp_base<index> {};
template <std::size_t index, typename IndexSequence>
struct deduct;
template <std::size_t index, std::size_t... indices>
struct deduct<index, std::index_sequence<indices...>>
{
template <typename Tuple>
struct deduct_impl;
template <typename T, typename... R>
struct deduct_impl<std::tuple<itp_base<indices>..., itp<index, T>, R...>>
{
using type = T;
};
};
template <std::size_t index, typename... Types>
class deducer
{
private:
static_assert( index < sizeof...( Types ), "deducer::index out of bounds" );
template <typename IndexSequence>
struct deducer_impl;
template <std::size_t... indices>
struct deducer_impl<std::index_sequence<indices...>>
{
using type = typename deduct<index, std::make_index_sequence<index>
>::template deduct_impl
<
std::tuple
<
std::conditional_t
<
std::is_base_of<itp_base<indices>, itp<index, Types>>::value,
itp<index, Types>,
itp_base<indices>
>...
>
>::type;
};
public:
using type = typename deducer_impl<
std::make_index_sequence<sizeof...( Types )>>::type;
};
Convenience aliases
template <std::size_t index, typename Tuple>
struct tuple_element;
template <std::size_t index, typename... Types>
struct tuple_element<index, std::tuple<Types...>> : deducer<index, Types...> {};
template <std::size_t index, typename... Types>
using tuple_element_t = typename tuple_element<index, Types...>::type;
Test case
#include <iostream>
#include <string>
int main()
{
using tuple_t = std::tuple<int, void, std::string>;
static_assert( std::is_same<tuple_element_t<2, tuple_t>, std::string>::value, "!" );
}