使用可变参数模板时,如何获取第 n 个参数类型?
When using variadric template, how to get nth arguments type?
我想要一个包含 n
值的 class,就像 std::tuple
一样。不过,我不能完全使用元组,因为在获取值时还有额外的逻辑——它们是按需提供的。
请考虑这个class我写的例子:
// 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> >();
}
};
我希望它的工作方式是用户只需调用 int myVal = GetValue<1>
,给定 class 模板参数 ResultRow<bool, int>
。为此,我需要能够将模板参数的索引转换为类型。
我该怎么做?
一种方法是将可变参数转发到一个元组中,然后使用 std::get
,例如:
#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';
}
您可以借助递归继承类型特征从参数包中获取类型。
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;
然后像这样使用它
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...> >();
}
};
我不认为我们应该包装stl的tuple_element
,但你可以:
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;
更好的解决方案:
tuple_element
似乎总是通过非常慢的递归继承来实现(我已经在 MSVC、gcc、clang 上测试过它们的许多版本——我仍然不知道它们为什么使用递归! ).它也不适用于 tuple
以外的任何东西,这很不幸。因此,我们将为任何具有类型参数(我称之为“包”)的 class 制作一些通用的东西。
下面我们针对这个特定问题推断 Julius 的 great answer。有关标准继承、多重继承和 tuple_element
性能的讨论,请参阅他们的回答。这里我们使用多重继承:
#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>>;
然后我们可以这样使用:
#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>);
}
我想要一个包含 n
值的 class,就像 std::tuple
一样。不过,我不能完全使用元组,因为在获取值时还有额外的逻辑——它们是按需提供的。
请考虑这个class我写的例子:
// 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> >();
}
};
我希望它的工作方式是用户只需调用 int myVal = GetValue<1>
,给定 class 模板参数 ResultRow<bool, int>
。为此,我需要能够将模板参数的索引转换为类型。
我该怎么做?
一种方法是将可变参数转发到一个元组中,然后使用 std::get
,例如:
#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';
}
您可以借助递归继承类型特征从参数包中获取类型。
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;
然后像这样使用它
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...> >();
}
};
我不认为我们应该包装stl的tuple_element
,但你可以:
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;
更好的解决方案:
tuple_element
似乎总是通过非常慢的递归继承来实现(我已经在 MSVC、gcc、clang 上测试过它们的许多版本——我仍然不知道它们为什么使用递归! ).它也不适用于 tuple
以外的任何东西,这很不幸。因此,我们将为任何具有类型参数(我称之为“包”)的 class 制作一些通用的东西。
下面我们针对这个特定问题推断 Julius 的 great answer。有关标准继承、多重继承和 tuple_element
性能的讨论,请参阅他们的回答。这里我们使用多重继承:
#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>>;
然后我们可以这样使用:
#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>);
}