1

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?

Tomáš Zato
  • 50,171
  • 52
  • 268
  • 778
  • Have a look at [this great answer](https://stackoverflow.com/a/47442393/8658157). It uses multi-inheritance which is actually really fast. – Elliott Oct 15 '21 at 06:33
  • I'm not 100% sure I've understood it correctly, but perhaps you could instead use a `std::tuple` of value getter functors to make the actual _obtaining values on demand_ working? – Ted Lyngmo Oct 15 '21 at 06:38
  • @TedLyngmo I am not entirely sure what would that like - to populate the functor types in the tuple, I'd need to know the types they should return, and I'm back where I started. – Tomáš Zato Oct 15 '21 at 06:41
  • @TomášZato-ReinstateMonica Hmm, ok, yeah. – Ted Lyngmo Oct 15 '21 at 06:47
  • *"I can't quite use tuple though"*. Don't understand the reason, `std::get(tuple)` is C++11, `std::get(tuple)` is C++14. And it seems I am not alone, as 3/4 of the answers use `std::tuple`. – Jarod42 Oct 15 '21 at 07:39
  • @Jarod42 Well, I didn't know it could be used the way many answers have demonstrated. – Tomáš Zato Oct 16 '21 at 10:50
  • @Elliott What is missing from the example? `TVal valueGetter()` in my case will be a method that fetches NTH column from a database query result row. – Tomáš Zato Oct 16 '21 at 10:50
  • @TomášZato-ReinstateMonica, well, what's `NoCopy`? Also, `I didn't know it could be used the way many answers have demonstrated`, well I provided godbolt links to demonstrate that, and also to explain why recursive inheritance (used in the accepted answer) is a bad idea - if you don't care about speed you should just wrap a `tuple`. – Elliott Oct 16 '21 at 11:11
  • @Elliott I'm sorry, I honestly didn't consider performance when accepting the answer, I don't really yet understand the matter at hand. I will review this. And yes, you provided links to demonstrate how to use the tuple, but that was after I asked the question - I was only explaining that when I asked the question, I didn't know how to use tuple for this, or even that you can. – Tomáš Zato Oct 16 '21 at 11:18
  • @TomášZato-ReinstateMonica: Risk with such false-requirement is that you can have missing answers and possibly just *"suboptimal"* answers. – Jarod42 Oct 17 '21 at 14:26

3 Answers3

5

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';
}
Ton van den Heuvel
  • 10,157
  • 6
  • 43
  • 82
  • 1
    [`std::forward_as_tuple`](https://en.cppreference.com/w/cpp/utility/tuple/forward_as_tuple) may also be useful in the more generic scenario. – Ted Lyngmo Jun 21 '22 at 14:58
4

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...> >();
  }
};
songyuanyao
  • 169,198
  • 16
  • 310
  • 405
1

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

Elliott
  • 2,603
  • 2
  • 18
  • 35