提取 class 的模板参数并迭代它们的最紧凑的方法是什么?
What is the most compact way to extract the template arguments of a class and iterate over them?
在下面的小程序中,我展示了我目前用于提取 class 的模板参数并通过递归辅助函数对其进行迭代的解决方案。
我想知道是否有更简洁的方法来做到这一点,正如我在下面评论中的伪代码中所解释的那样。
template <int...Is> struct Pack {};
template <int I> struct B
{
static void foo() { std::cout << I << "\n"; }
};
// recursive helper function, also used to extract the parameter pack arguments
template <int I, int...Is>
void foo_helper( Pack<I, Is...>&& )
{
B<I>::foo();
foo_helper( Pack<Is...>{} );
}
// terminate recursion
void foo_helper( Pack<>&& ) {}
struct A
{
typedef Pack<1,3,5> ints;
static void foo()
{
// this is what I do
foo_helper(ints{});
// this is what I would like to do, ideally in one single line
// 1) extract the template arguments pack from ints, without creating an helper function for that
// 2) iterate on the template arguments of the pack without a recursive helper
// In pseudocode, something like:
// (B<IterateOver<ArgumentsOf<ints>>>::foo());
}
};
int main()
{
A::foo();
}
您可以将 foo_for_each
函数添加到 Pack
:
template <int...Is> struct Pack {
template <template <int> class T>
static void foo_for_each () {
std::initializer_list<int> { (T<Is>::foo(),0)... } ;
}
};
那么你只需要写:
ints::foo_for_each<B>();
这将为包中的每个 N
调用 B<N>::foo
。
正如 Yakk 所建议的,您可以传入一个 lambda,它获取一个标记类型作为参数来创建一个通用的 Pack::for_each
:
template <typename T> struct tag { using type = T; };
template <typename T> using type_t = typename T::type;
template <int...Is> struct Pack {
template <template <int> class T, typename Func>
static void for_each (Func&& func) {
std::initializer_list<int> {
((std::forward<Func>(func)(tag<T<Is>>{})) 0)...
} ;
}
};
那么你可以这样调用:
auto call_foo = [](auto tag) { type_t<decltype(tag)>::foo(); };
ints::for_each<B>(call_foo);
如果您想要 runtime-iteration 的可变参数包,您可以将 std::array
附加到您的 struct Pack
作为:
template <int...Is> struct Pack {
std::array<int, sizeof...(Is)> arr = {{Is...}};
};
然后迭代为:
static void foo() {
for(auto && i : ints{}.arr) std::cout << i << " ";
}
你在这里写的东西简直太奇怪了,你甚至在哪里找到这个实现
刚性 ?
你需要一个辅助函数,这是事实,你可以解决它
不知何故,但我不明白那有什么意义。
目前唯一的解决方案是使用 Clang 3.6,他们已经实现了
新的语法,允许你写这样的东西。
// 我很确定,这就是语法,它被称为折叠表达式
// 你可以在这里阅读更多相关信息:
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4295.html
template<typename ... Type>
auto sum(Type ... argument)
{
return (... + argument);
}
在任何其他编译器中,实现它的方法是编写两个简单的函数
template<typename Tail>
auto sum(Tail tail)
{
return tail;
}
template<typename Head, typename ... Tail>
auto sum(Head head, Tail ... tail)
{
return head + sum(tail);
}
它接受任何支持 + 的东西,所以字符串、整数、双精度数可能会起作用
还有一些,但你明白了它的要点。
您的示例如下所示
template<typename Tail>
void print(Tail tail)
{
cout << tail << endl;
}
template<typename Head, typename ... Tail>
void print(Head head, Tail ... tail)
{
cout << head;
print(tail...);
}
用法:
print(1, 3.14, "something", string{"yeye"}, 52);
或
sum(1, 512, 55, 91);
还有一些其他方法可以使用可变参数模板,就像这里的这个人描述的那样,
太多了,我不能把它放在这里,所以我就 link:
迭代模板的参数有点困难,因为你必须使用
一些真正的编译器魔法和 index_sequence.
我这里有一个例子,因为我一直在胡闹
最近用它。
template<typename InputTuple, std::size_t ... N>
void tupleIteratorImpl(InputTuple& input, std::index_sequence<N...>)
{
// DO WHATEVER YOU WANT HERE, but the structure is
FUNCTION(/* pass all of the template parameters as arguments */, std::get<N>(input)...);
// and FUNCTION has to have the structure of the examples from point 1.
// but with this, you can already do pretty much anything you imagine
// even at compile time
}
template<typename InputTuple, typename Indices = std::make_index_sequence<std::tuple_size<InputTuple>::value>>
void tupleIterator(InputTuple& input)
{
tupleIteratorImpl(input, Indices());
}
c++17 中已经包含了一个函数,叫做 apply,这里是文档:
http://en.cppreference.com/w/cpp/experimental/apply 甚至还有一些示例代码。
希望这能回答您的一些问题。
如果您想进行元编程,请从类型开始。如果您需要 non-type 模板参数,请尽快将它们移至类型。
下面,我先把Pack<1,2,3>
转换成types< std::integral_constant<int, 1>, std::integral_constant<int, 2>, std::integral_constant<int, 3> >
。这是与您的整数包明显对应的类型列表。
然后,我介绍一个标签类型模板。这是一种类型,"carries"另一种类型,但它本身是无状态的。作为奖励,您可以从模板实例的值中提取类型。
第三,我编写了一个 "for each type" 函数,它接受一个 lambda 和一组类型,然后为每个类型调用一次 lambda,传入一个标记类型。
在 lambda 的主体中,我们可以通过在标签变量(或辅助宏)上使用 decltype
来提取传递的类型。
我们将它们链接在一起,从传递的标签类型中我们可以提取原始包中的整数。
结果是您可以将其注入您的代码:
for_each_type( [&](auto tag){
constexpr int i = TAG_TYPE(tag){};
// use i
}, ints_as_types_t<ints>{} );
在你的方法中间,处理整数 "inline"。
如果我们只想解决您的特定问题,我们会少做一些样板文件,但我喜欢这种通用性。
template<class...>struct types{using type=types;};
template <int...Is> struct Pack {};
template<class pack> struct ints_as_types;
template<class pack>
using ints_as_types_t=typename ints_as_types<pack>::type;
template<class T, template<T...>class pack, T...ts>
struct ints_as_types<pack<ts...>> {
using type=types<std::integral_constant<T,ts>...>;
};
现在我们可以做:
using pack = ints_as_types_t<Pack<1,2,3>>;
和 pack
是类型列表,而不是整数列表。
现在一些 hana-style 元编程:(使用值而不是纯类型进行元编程)
template<class T>struct tag_t{using type=T; constexpr tag_t(){};};
template<class T>constexpr tag_t<T> tag={};
template<class Tag>using type_t=typename Tag::type;
#define TAG_TYPE(...) type_t<std::decay_t<decltype(__VA_ARGS__)>>;
template<class F, class...Ts>
void for_each_type(F&& f, types<Ts...>) {
using discard=int[];
(void)discard{ 0, ((
f(tag<Ts>)
),void(),0)...};
}
它可以让您遍历类型集合。
for_each_type( [&](auto tag){
constexpr int i = TAG_TYPE(tag){};
// use i
}, ints_as_types_t<ints>{} );
为您提供一个 lambda,其中每个列表中的类型都有一个 constexpr int i
。
上述一系列工作将您的整数列表提升为类型列表,因为仅使用类型可以减少元编程 special-case。你可以跳过那个提升,写一个 for_each_integer
直接接受 Pack<int...>
,代码更少,但它对我来说似乎没那么有用。
这是我能想到的最短的:
#include <iostream>
template<int... Is>
struct Pack;
template <int I> struct B
{
static void foo() { std::cout << I << "\n"; }
};
template<typename PACK> struct unpack;
template<int...Is>
struct unpack<Pack<Is...>>
{
template<template<int> class T>
static void call()
{
using swallow = int[sizeof...(Is)];
(void) swallow{(T<Is>::foo(), 0)...};
}
};
struct A
{
typedef Pack<1,3,5> ints;
static void foo()
{
unpack<ints>::call<B>();
}
};
int main()
{
A::foo();
}
在下面的小程序中,我展示了我目前用于提取 class 的模板参数并通过递归辅助函数对其进行迭代的解决方案。
我想知道是否有更简洁的方法来做到这一点,正如我在下面评论中的伪代码中所解释的那样。
template <int...Is> struct Pack {};
template <int I> struct B
{
static void foo() { std::cout << I << "\n"; }
};
// recursive helper function, also used to extract the parameter pack arguments
template <int I, int...Is>
void foo_helper( Pack<I, Is...>&& )
{
B<I>::foo();
foo_helper( Pack<Is...>{} );
}
// terminate recursion
void foo_helper( Pack<>&& ) {}
struct A
{
typedef Pack<1,3,5> ints;
static void foo()
{
// this is what I do
foo_helper(ints{});
// this is what I would like to do, ideally in one single line
// 1) extract the template arguments pack from ints, without creating an helper function for that
// 2) iterate on the template arguments of the pack without a recursive helper
// In pseudocode, something like:
// (B<IterateOver<ArgumentsOf<ints>>>::foo());
}
};
int main()
{
A::foo();
}
您可以将 foo_for_each
函数添加到 Pack
:
template <int...Is> struct Pack {
template <template <int> class T>
static void foo_for_each () {
std::initializer_list<int> { (T<Is>::foo(),0)... } ;
}
};
那么你只需要写:
ints::foo_for_each<B>();
这将为包中的每个 N
调用 B<N>::foo
。
正如 Yakk 所建议的,您可以传入一个 lambda,它获取一个标记类型作为参数来创建一个通用的 Pack::for_each
:
template <typename T> struct tag { using type = T; };
template <typename T> using type_t = typename T::type;
template <int...Is> struct Pack {
template <template <int> class T, typename Func>
static void for_each (Func&& func) {
std::initializer_list<int> {
((std::forward<Func>(func)(tag<T<Is>>{})) 0)...
} ;
}
};
那么你可以这样调用:
auto call_foo = [](auto tag) { type_t<decltype(tag)>::foo(); };
ints::for_each<B>(call_foo);
如果您想要 runtime-iteration 的可变参数包,您可以将 std::array
附加到您的 struct Pack
作为:
template <int...Is> struct Pack {
std::array<int, sizeof...(Is)> arr = {{Is...}};
};
然后迭代为:
static void foo() {
for(auto && i : ints{}.arr) std::cout << i << " ";
}
你在这里写的东西简直太奇怪了,你甚至在哪里找到这个实现 刚性 ?
你需要一个辅助函数,这是事实,你可以解决它 不知何故,但我不明白那有什么意义。
目前唯一的解决方案是使用 Clang 3.6,他们已经实现了 新的语法,允许你写这样的东西。
// 我很确定,这就是语法,它被称为折叠表达式
// 你可以在这里阅读更多相关信息:
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4295.htmltemplate<typename ... Type> auto sum(Type ... argument) { return (... + argument); }
在任何其他编译器中,实现它的方法是编写两个简单的函数
template<typename Tail> auto sum(Tail tail) { return tail; } template<typename Head, typename ... Tail> auto sum(Head head, Tail ... tail) { return head + sum(tail); }
它接受任何支持 + 的东西,所以字符串、整数、双精度数可能会起作用 还有一些,但你明白了它的要点。
您的示例如下所示
template<typename Tail> void print(Tail tail) { cout << tail << endl; } template<typename Head, typename ... Tail> void print(Head head, Tail ... tail) { cout << head; print(tail...); }
用法:
print(1, 3.14, "something", string{"yeye"}, 52);
或
sum(1, 512, 55, 91);
还有一些其他方法可以使用可变参数模板,就像这里的这个人描述的那样, 太多了,我不能把它放在这里,所以我就 link:
迭代模板的参数有点困难,因为你必须使用 一些真正的编译器魔法和 index_sequence.
我这里有一个例子,因为我一直在胡闹 最近用它。
template<typename InputTuple, std::size_t ... N> void tupleIteratorImpl(InputTuple& input, std::index_sequence<N...>) { // DO WHATEVER YOU WANT HERE, but the structure is FUNCTION(/* pass all of the template parameters as arguments */, std::get<N>(input)...); // and FUNCTION has to have the structure of the examples from point 1. // but with this, you can already do pretty much anything you imagine // even at compile time } template<typename InputTuple, typename Indices = std::make_index_sequence<std::tuple_size<InputTuple>::value>> void tupleIterator(InputTuple& input) { tupleIteratorImpl(input, Indices()); }
c++17 中已经包含了一个函数,叫做 apply,这里是文档: http://en.cppreference.com/w/cpp/experimental/apply 甚至还有一些示例代码。
希望这能回答您的一些问题。
如果您想进行元编程,请从类型开始。如果您需要 non-type 模板参数,请尽快将它们移至类型。
下面,我先把Pack<1,2,3>
转换成types< std::integral_constant<int, 1>, std::integral_constant<int, 2>, std::integral_constant<int, 3> >
。这是与您的整数包明显对应的类型列表。
然后,我介绍一个标签类型模板。这是一种类型,"carries"另一种类型,但它本身是无状态的。作为奖励,您可以从模板实例的值中提取类型。
第三,我编写了一个 "for each type" 函数,它接受一个 lambda 和一组类型,然后为每个类型调用一次 lambda,传入一个标记类型。
在 lambda 的主体中,我们可以通过在标签变量(或辅助宏)上使用 decltype
来提取传递的类型。
我们将它们链接在一起,从传递的标签类型中我们可以提取原始包中的整数。
结果是您可以将其注入您的代码:
for_each_type( [&](auto tag){
constexpr int i = TAG_TYPE(tag){};
// use i
}, ints_as_types_t<ints>{} );
在你的方法中间,处理整数 "inline"。
如果我们只想解决您的特定问题,我们会少做一些样板文件,但我喜欢这种通用性。
template<class...>struct types{using type=types;};
template <int...Is> struct Pack {};
template<class pack> struct ints_as_types;
template<class pack>
using ints_as_types_t=typename ints_as_types<pack>::type;
template<class T, template<T...>class pack, T...ts>
struct ints_as_types<pack<ts...>> {
using type=types<std::integral_constant<T,ts>...>;
};
现在我们可以做:
using pack = ints_as_types_t<Pack<1,2,3>>;
和 pack
是类型列表,而不是整数列表。
现在一些 hana-style 元编程:(使用值而不是纯类型进行元编程)
template<class T>struct tag_t{using type=T; constexpr tag_t(){};};
template<class T>constexpr tag_t<T> tag={};
template<class Tag>using type_t=typename Tag::type;
#define TAG_TYPE(...) type_t<std::decay_t<decltype(__VA_ARGS__)>>;
template<class F, class...Ts>
void for_each_type(F&& f, types<Ts...>) {
using discard=int[];
(void)discard{ 0, ((
f(tag<Ts>)
),void(),0)...};
}
它可以让您遍历类型集合。
for_each_type( [&](auto tag){
constexpr int i = TAG_TYPE(tag){};
// use i
}, ints_as_types_t<ints>{} );
为您提供一个 lambda,其中每个列表中的类型都有一个 constexpr int i
。
上述一系列工作将您的整数列表提升为类型列表,因为仅使用类型可以减少元编程 special-case。你可以跳过那个提升,写一个 for_each_integer
直接接受 Pack<int...>
,代码更少,但它对我来说似乎没那么有用。
这是我能想到的最短的:
#include <iostream>
template<int... Is>
struct Pack;
template <int I> struct B
{
static void foo() { std::cout << I << "\n"; }
};
template<typename PACK> struct unpack;
template<int...Is>
struct unpack<Pack<Is...>>
{
template<template<int> class T>
static void call()
{
using swallow = int[sizeof...(Is)];
(void) swallow{(T<Is>::foo(), 0)...};
}
};
struct A
{
typedef Pack<1,3,5> ints;
static void foo()
{
unpack<ints>::call<B>();
}
};
int main()
{
A::foo();
}