I don't think that we should wrap stl's tuple_element, but you could:
template <std::size_t, class>
struct nth_of_pack;
template <std::size_t N, template <class...> class Pack, class ... Ts>
struct nth_of_pack <N, Pack<Ts...>>
: std::tuple_element<N, std::tuple<Ts...>> {};
template <std::size_t N, class Pack>
using nth_of_pack_t = typename nth_of_pack<N, Pack>::type;
Demo
Better solution:
tuple_element seems to always be implemented with recursive inheritance which is extremely slow (I've tested on MSVC, gcc, clang with many of their versions - I still don't know why they use recursion!). It also doesn't work with anything other than tuple, which is unfortunate. So instead we'll make something generic for any class with type arguments (which I call a "pack").
Below we extrapolate Julius's great answer for this specific problem. See their answer for discussion on performance regarding standard inheritance, multi-inheritance, and tuple_element. Here we use multi-inheritance:
#include <utility>
template <class T>
struct tag
{
using type = T;
};
template <class T>
using result_t = typename T::type;
////////////////////////////////////////////////////////////////////////////////
template<std::size_t, std::size_t, class>
struct type_if_equal {};
template<std::size_t n, class T>
struct type_if_equal<n, n, T> : tag<T> {};
////////////////////////////////////////////////////////////////////////////////
template<std::size_t n, class Is, class... Ts>
struct select_nth_implementation;
template<std::size_t n, std::size_t... is, class... Ts>
struct select_nth_implementation<n, std::index_sequence<is...>, Ts...>
: type_if_equal<n, is, Ts>... {};
template<std::size_t n, class... Ts>
struct select_nth : select_nth_implementation<
n, std::index_sequence_for<Ts...>, Ts...> {};
template<std::size_t n, class... Ts>
using select_nth_t = result_t<select_nth<n, Ts...>>;
////////////////////////////////////////////////////////////////////////////////
template <std::size_t, class>
struct nth_of_pack;
template <std::size_t N, template <class...> class Pack, class ... Ts>
struct nth_of_pack <N, Pack<Ts...>> : select_nth<N, Ts...> {};
template <std::size_t N, class Pack>
using nth_of_pack_t = result_t<nth_of_pack<N, Pack>>;
We can then use like so:
#include <type_traits>
template <class...>
class foo;
int main () {
using my_tuple = foo<int, bool, char, double>;
using second_type = nth_of_pack_t<2, my_tuple>;
static_assert(std::is_same_v<second_type, char>);
}
Demo