Tomáš Zato
Tomáš Zato

Reputation: 53119

When using variadric template, how to get nth arguments type?

I want to have a class that holds n values, much like std::tuple. I can't quite use tuple though, as there is additional logic in obtaining the values - they are on demand.

Please consider this class I wrote as an example:

// somewhere else
template<typename TVal>
TVal valueGetter() { ... };

template<typename ...TColValue>
class ResultRow
{
public:
  template<unsigned int TIndex>
  get_nth_from_variadric<TIndex, TColValue> GetValue() const
  {
    return valueGetter<get_nth_from_variadric<TIndex, TColValue> >();
  }
  

};

What I want it to work like is that the user simply calls int myVal = GetValue<1>, given class template params ResultRow<bool, int>. For this to work, I need to be able to convert the index of the template argument into type.

How can I do that?

Upvotes: 1

Views: 952

Answers (3)

Elliott
Elliott

Reputation: 2623

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

Upvotes: 2

songyuanyao
songyuanyao

Reputation: 172864

You can get the type from parameter pack with the help of a recursive inheriting type trait.

template<unsigned int TIndex, typename ...TColValue>
struct get_nth_from_variadric_type;

template<unsigned int TIndex, typename Head, typename... Tail >
struct get_nth_from_variadric_type<TIndex, Head, Tail...>
    : get_nth_from_variadric_type<TIndex-1, Tail...> { };

template<typename Head, typename... Tail>
struct get_nth_from_variadric_type<0, Head, Tail...> {
   using type = Head;
};

template<unsigned int TIndex, typename ...TColValue>
using get_nth_from_variadric = typename get_nth_from_variadric_type<TIndex, TColValue...>::type;

Then use it like

template<typename ...TColValue>
class ResultRow
{
public:
  template<unsigned int TIndex>
  get_nth_from_variadric<TIndex, TColValue...> GetValue() const
  {
    return valueGetter<get_nth_from_variadric<TIndex, TColValue...> >();
  }
};

Upvotes: 4

Ton van den Heuvel
Ton van den Heuvel

Reputation: 10528

One approach would be to forward the variadic arguments into a tuple and then use std::get, for example:

#include <iostream>
#include <tuple>

template<size_t N, typename... Args>
auto f(Args&&... args)
{
  return std::get<N>(std::tuple{std::forward<Args>(args)...});
}

int main(void)
{
  std::cout << f<0>("Hello", "world") << ' ' << f<1>("Hello", "world") << f<0>('!') << '\n';
}

Upvotes: 5

Related Questions