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 NoCopy
{
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?
CodePudding user response:
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';
}
CodePudding user response:
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...> >();
}
};
CodePudding user response:
You can just use tuple_element_t
to build this:
template<std::size_t N, typename... Args>
using get_nth_from_variadric = std::tuple_element_t<N, std::tuple<Args...>>;
CodePudding user response:
You could use stl's tuple_element_t
:
#include <tuple>
#include <type_traits>
int main () {
using my_tuple = std::tuple<int, bool, char, double>;
using second_type = std::tuple_element_t<2, my_tuple>;
static_assert(std::is_same_v<second_type, char>);
}
However, it seems to always be implemented with recursive inheritance which is extremely slow (I've tested on MSVC, gcc, clang on many versions - I still don't know why they use recursion!). Also, it'd be nicer to have something generic to any class with type arguments (which I call a "pack").
Below we extrapolate Julius's great answer for this specific problem. See Julius' answer for the discussion on performance regarding standard inheritance, multi-inheritance, and tuple_element
. This uses 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 <tuple>
#include <type_traits>
int main () {
using my_tuple = std::tuple<int, bool, char, double>;
using second_type = nth_of_pack_t<2, my_tuple>;
static_assert(std::is_same_v<second_type, char>);
}