如何使用可变参数模板来展平类型树
How can I use Variadic templates to flatten a tree of types?
我有一个这样的结构:
template<typename... Ts>
struct List {}
typedef List<char,List<int,float,List<int,unsigned char>>,List<unsigned,short>> MyList;
我想基本上将其扁平化为一个列表。最好的方法是什么?我想如果我摆弄它足够长的时间,我可以用递归做一些东西,但有些事情告诉我应该有更好的方法。
作为上述树的结果,我想要的应该类似于这个:
typedef List<char,int,float,int,unsigned char,unsigned,short> FlattenedList;
这是我的第一次尝试:
template<typename... Ts>
struct List{};
template<typename... Ts>
struct FlattenTree{
typedef List<Ts...> Type;
};
template<typename... Ts, typename... Us, typename... Vs>
struct FlattenTree<Ts...,List<Us...>,Vs...>{
typedef typename FlattenTree<Ts..., Us..., Vs...>::Type Type;
};
但它会导致此错误:error C3515: if an argument for a class template partial specialization is a pack expansion it shall be the last argument
Rici在这里指出了MSVC2013在抱怨什么,所以这里没有编译器错误:
§ 14.8.2.5(从类型推导模板参数)第 5 段列出了无法推导模板参数的上下文。相关的是列表中的最后一个:
— A function parameter pack that does not occur at the end of the parameter-declaration-clause.
更新:
我想可以在最后放一个虚拟参数,继续将第一个参数移动到末尾或将其扩展到前面,如果它是一个列表,并且专注于第一个参数是我的虚拟参数以停止递归。不过,编译器似乎需要做很多工作才能使列表扁平化。
namespace Detail{
struct MyMagicType {};
template<typename T, typename... Ts>
struct FlattenTree{
typedef typename FlattenTree<Ts..., T>::Type Type;
};
template<typename... Ts>
struct FlattenTree<MyMagicType,Ts...>{ //termination case
typedef List<Ts...> Type;
};
template<typename... Ts, typename... Us>
struct FlattenTree<List<Ts...>, Us...>{
typedef typename FlattenTree<Ts..., Us...>::Type Type;
}; //expand Ts to front because they may hold more nested Lists
}
template<typename... Ts>
struct FlattenTree{
typedef typename Detail::FlattenTree<Ts...,Detail::MyMagicType>::Type Type;
};
这适用于MSVC2013但我认为这不是最好的方法,因为我需要一个虚拟类型,并且它会给编译器带来很多负载。我想将其与包含 500+ 元素的列表一起使用。
另一种方法是使用帮助程序类和累加器列表而不是MyMagicType
。我们从一个空List<>
开始,然后用输入列表中的类型填充它:
#include <type_traits>
template <class... Ts> struct List {};
// first parameter - accumulator
// second parameter - input list
template <class T, class U>
struct flatten_helper;
// first case - the head of the List is List too
// expand this List and continue
template <class... Ts, class... Heads, class... Tail>
struct flatten_helper<List<Ts...>, List<List<Heads...>, Tail...>> {
using type = typename flatten_helper<List<Ts...>, List<Heads..., Tail...>>::type;
};
// second case - the head of the List is not a List
// append it to our new, flattened list
template <class... Ts, class Head, class... Tail>
struct flatten_helper<List<Ts...>, List<Head, Tail...>> {
using type = typename flatten_helper<List<Ts..., Head>, List<Tail...>>::type;
};
// base case - input List is empty
// return our flattened list
template <class... Ts>
struct flatten_helper<List<Ts...>, List<>> {
using type = List<Ts...>;
};
// wrapper around flatten_helper
template <class T> struct flatten;
// start with an empty accumulator
template <class... Ts>
struct flatten<List<Ts...>> {
using type = typename flatten_helper<List<>, List<Ts...>>::type;
};
auto main() -> int {
using Types = List<int, List<float, List<double, List<char>>>>;
using Flat = flatten<Types>::type;
static_assert(std::is_same<Flat, List<int, float, double, char>>::value, "Not the same");
}
您的解决方案非常优雅 IMO,这是我头顶上的另一个解决方案:
// the tuple-like class we want to flatten
// (i.e. the node of the tree, with several children as template parameters)
template<class... TT>
struct List
{};
// a join metafunction. Joins multiple Lists into a single List
// e.g. List<TT1...>, List<TT2...>, etc., List<TTN...>
// => List<TT1..., TT2..., etc., TTN...>
// requires: all template arguments are `List<..>`s
template<class... TT>
struct join
{
using type = List<>; // end recursion for no parameters
};
template<class... TT>
struct join<List<TT...>>
{
using type = List<TT...>; // end recursion for a single parameter
};
template<class... TT0, class... TT1, class... TT2>
struct join<List<TT0...>, List<TT1...>, TT2...>
{
// join two adjacent lists into one, recurse
// by joining the two lists `List<TT0...>` and `List<TT1...>`,
// we get one template argument less for the next instantiation of `join`
// this recurs until there's only one argument left, which then
// matches the specialization `struct join<List<TT...>>`
using type = typename join< List<TT0..., TT1...>, TT2... > :: type;
};
// the flatten metafunction
// guarantees (all specializations): the nested `type` is a flat `List<..>`
template<class T>
struct flatten
{
// because of the partial specialization below,
// this primary template is only used if `T` is not a `List<..>`
using type = List<T>; // wrap the argument in a `List`
};
template<class... TT>
struct flatten<List<TT...>> // if the argument is a `List` of multiple elements
{
// then flatten each element of the `List` argument
// and join the resulting `List<..>`s
using type = typename join<typename flatten<TT>::type...>::type;
// ex. the argument is `List<List<int>, List<double>>`
// then `TT...` is deduced to `List<int>, List<double>`
// `List<int>` flattened is `List<int>`, similarly for `List<double>`
// `join< List<int>, List<double> >` yields `List<int, double>`
};
用法和测试代码:
#include <iostream>
template<class T>
void print(T)
{
std::cout << __PRETTY_FUNCTION__ << "n"; // NON-STANDARD
}
int main()
{
typedef List<char,List<int,float,List<int,unsigned char>>,
List<unsigned,short>> MyList;
print( flatten<MyList>::type{} );
}
我确定最简单的方法是使用 boost::MPL ;)
这是我使用通用"元树遍历"的解决方案:
#include <iostream>
#include <type_traits>
#include <typeinfo>
template <typename T>
struct HasChildren : std::false_type {};
template <template <typename...> class P, typename... Types>
struct HasChildren<P<Types...>> : std::true_type {};
template <typename, typename> struct Visit;
template <typename, typename, bool> struct VisitHelper;
template <typename, typename> struct LeafAction;
// Here the role of P2<Visited...> is simply to allow LeafAction to carry out its function. It is not native to the tree structure itself.
template <template <typename...> class P1, template <typename...> class P2, typename First, typename... Rest, typename... Visited>
struct Visit<P1<First, Rest...>, P2<Visited...>> :
VisitHelper<P1<First, Rest...>, P2<Visited...>, HasChildren<First>::value> {};
template <template <typename...> class P1, template <typename...> class P2, typename... Visited>
struct Visit<P1<>, P2<Visited...>> { // End of recursion. Every leaf in the tree visited.
using result = P2<Visited...>;
};
// Here First has children, so visit its children, after which visit Rest...
template <template <typename...> class P1, template <typename...> class P2, typename First, typename... Rest, typename... Visited>
struct VisitHelper<P1<First, Rest...>, P2<Visited...>, true> : Visit<P1<Rest...>, typename Visit<First, P2<Visited...>>::result> {}; // Visit the "subtree" First, and then after that visit Rest... Need to use ::result so that when visiting Rest..., the latest value of the P2<Visited...> pack is used.
// Here First has no children, so the "leaf action" is carried out.
template <template <typename...> class P1, template <typename...> class P2, typename First, typename... Rest, typename... Visited>
struct VisitHelper<P1<First, Rest...>, P2<Visited...>, false> : LeafAction<P1<First, Rest...>, P2<Visited...>> {};
// As a simple example, the leaf action shall simply be appending its type to P<Visited...>.
template <template <typename...> class P1, template <typename...> class P2, typename First, typename... Rest, typename... Visited>
struct LeafAction<P1<First, Rest...>, P2<Visited...>> : Visit<P1<Rest...>, P2<Visited..., First>> {}; // Having visited First, now visit Rest...
template <typename> struct VisitTree;
template <template <typename...> class P, typename... Types>
struct VisitTree<P<Types...>> : Visit<P<Types...>, P<>> {};
// ---------------------------- Testing ----------------------------
template <typename...> struct Pack;
template <typename Last>
struct Pack<Last> {
static void print() {std::cout << typeid(Last).name() << std::endl;}
};
template <typename First, typename... Rest>
struct Pack<First, Rest...> {
static void print() {std::cout << typeid(First).name() << ' '; Pack<Rest...>::print();}
};
template <typename...> struct Group;
template <typename...> struct Wrap;
struct Object {};
int main() {
VisitTree<
Pack<Pack<int, Object, double>, bool, Pack<char, Pack<int, double, Pack<char, Pack<char, long, short>, int, Object>, char>, double>, long>
>::result::print(); // int Object double bool char int double char char long short int Object char double long
std::cout << std::boolalpha << std::is_same<
VisitTree<Pack<Wrap<int, Object, double>, bool, Group<char, Pack<int, double, Pack<char, Wrap<char, long, short>, int, Object>, char>, double>, long>>::result,
Pack<int, Object, double, bool, char, int, double, char, char, long, short, int, Object, char, double, long>
>::value << std::endl; // true
}
相关文章:
- 类型总是使用其大小存储在内存中吗
- 为什么在我的函数类型后使用引用运算符 (&) 允许我修改它返回的值?
- 使用 make 编译 MPI,几个命名空间错误,例如"错误:未知类型名称'使用'?
- 在模板化成员函数的返回类型中使用 std::enable_if 时的编译器差异
- Google Sparsehash 在类型上使用 realloc(),这很难复制
- 将 lower_bound/upper_bound 与 2 种不同的类型一起使用
- 根据动态选择类型C++模板使用情况
- 不允许将SDL_Cursor与unique_ptr:error不完整类型一起使用
- 为什么引用类型在使用临时对象访问时是左值
- 在枚举类型上使用std::max是不是一种糟糕的做法
- 将提升属性映射与捆绑类型一起使用
- 使用 std::future 的不完整类型无效使用
- 为什么在函数参数类型中使用模板参数包作为其模板参数列表无法显式指定
- 如何将模运算符与其他数据类型一起使用
- 澄清了双精度数据类型的使用
- 类型检测:使用variadic参数正确实现计算平均值的函数
- 如何使用%类型指令使用std ::变体类型
- C++ 在知道变量类型之前使用自动定义的变量
- 在任何类类型上使用模板方法中的 new
- 如何将STD :: Iterator与基本类型Uint8_t**使用