模板元编程:为什么平面类型是失败的

template metaprogramming :why flat type is failure

本文关键字:类型 失败 平面 为什么 编程      更新时间:2023-10-16

我想将树类型展平为平面类型。例:

typedef std::tuple<int,std::tuple<int,long>,int> tup; 
Flat<tup>::type=>std::tuple<int,int,long,int>

我使用:

template<typename T>
struct Flat
{
using type=T;
};
template <template <typename ...> class C,typename...ARGS>
struct Flat<C<ARGS...> >
{
using type=C<ARGS...>;
};
template <template <typename ...> class C,typename ...ARGS0,typename...ARGS1,typename ...ARGS2>
struct Flat<C<ARGS0...,C<ARGS1...>,ARGS2...> >
:Flat<C<ARGS0...,ARGS1...,ARGS2...> >
{
};
void test(){
typedef std::tuple<int,std::tuple<int,long>,int> tup;
static_assert(std::is_same<typename Flat<tup>::type,std::tuple<int,int,long,int> >::value,"");
}

但我仍然std::tuple<int,std::tuple<int,long>,int>...我使用 gcc 4.8.1

首先解释一下它是如何工作的:

这是一个前向声明,以便我们可以将flatten与单个类型一起使用。

template<class T>
struct flatten;

然后,我们专门flatten接受任何模板类型。C是一个模板模板参数,因此我们可以获取周围模板的类型。 例如,如果您将flatten与参数一起使用std::tuple<int, double>那么C将是std::tuple.

FArgs 用于检索传递的模板的参数列表。在我刚才提到的示例中,它将是int, double

template< template< typename ... > class C, typename ...FArgs>                  
struct flatten<C<FArgs...>>                                                     

flatten实现的其余部分现在始终可以访问C包装器类型并FArgs参数列表。

append的前向声明,用于将参数B追加到参数Target中的项列表

template< typename Target, typename B >                                  
struct append;                                                              

Target检索类型列表的append专业化(见上文)。 请注意,此处C仍然是std::tuple

因此,我们将向列表中再添加一个项,并通过应用上一个参数列表Args1然后添加T来创建新类型

template< typename ...Args1, typename T >
struct append<C<Args1...>, T> {
using type = C<Args1..., T>;
};

innerinner2的预先申报

inner用于循环访问提供的参数列表,并对每个参数应用inner2,如果类型是另一个带有参数的模板,则再次inner应用于类型。此模板必须是匹配的模板C

template< typename Target, typename ...Args >
struct inner;
template< typename Target, typename T >                                     
struct inner2;                                                              

inner2处理所有C模板类型,并在inner的帮助下递归遍历其参数列表

template< typename Target, typename ...Args>                                
struct inner2<Target, C<Args...>> {                                         
using type = typename inner<Target, Args...>::type;                     
};                                                                          

如果使用这种inner2专用化,则该类型将追加到最终结果中。

template< typename Target, typename T >                                     
struct inner2 {                                                             
using type = typename append<Target, T>::type;                          
};                                                                          

通过继承和应用每个参数来递归模板参数inner2

template< typename Target, typename T, typename ...Args>                    
struct inner<Target, T, Args...>                                            
: inner<typename inner2<Target, T>::type, Args...>                          
{};                                                                         

递归的结束条件。

template< typename Target, typename T >                                     
struct inner<Target, T>                                                     
{                                                                           
using type = typename inner2<Target, T>::type;                          
};                                                                          

在这里,从上面触发了整个事情,C<>指定了TargetFArgsflatten专用化提取的参数。

using type = typename inner<C<>, FArgs...>::type;                           
};

这里是整个代码:

#include <tuple>
#include <string>
template<class T>
struct flatten;
template< template< typename ... > class C, typename ...FArgs>                  
struct flatten<C<FArgs...>>                                                     
{                                                                               
template< typename Target, typename ...B >                                  
struct append;                                                              
template< typename ...Args1, typename T >                                   
struct append<C<Args1...>, T> {                                             
using type = C<Args1..., T>;                                            
};                                                                          
template< typename Target, typename ...Args >                               
struct inner;                                                               
template< typename Target, typename T >                                     
struct inner2;                                                              
template< typename Target, typename ...Args>                                
struct inner2<Target, C<Args...>> {                                         
using type = typename inner<Target, Args...>::type;                     
};                                                                          
template< typename Target, typename T >                                     
struct inner2 {                                                             
using type = typename append<Target, T>::type;                          
};                                                                          
template< typename Target, typename T, typename ...Args>                    
struct inner<Target, T, Args...>                                            
: inner<typename inner2<Target, T>::type, Args...>                          
{};                                                                         
template< typename Target, typename T >                                     
struct inner<Target, T>                                                     
{                                                                           
using type = typename inner2<Target, T>::type;                          
};                                                                          
using type = typename inner<C<>, FArgs...>::type;                           
};
int main() {                                                                    
typedef flatten<std::tuple<int, float, double>>::type first;                
static_assert(std::is_same<first, std::tuple<int, float, double>>::value, "First not the same");
typedef flatten<std::tuple<int, std::tuple<float, double>>>::type second;   
static_assert(std::is_same<second, std::tuple<int, float, double>>::value, "Second not the same");
typedef flatten<std::tuple<int, std::tuple<char const *>, std::tuple<std::tuple<float, int>, double>>>::type third;
static_assert(std::is_same<third, std::tuple<int, char const *, float, int, double>>::value, "Third not the same");
typedef flatten<std::tuple<int, std::tuple<std::tuple<std::tuple<std::tuple<char const *>>>>, std::tuple<std::tuple<float, int>, double>>>::type fourth;
static_assert(std::is_same<fourth, std::tuple<int, char const *, float, int, double>>::value, "Fourth not the same");
typedef flatten<std::tuple<int, std::tuple<std::tuple<std::tuple<std::tuple<std::string>>>>, std::tuple<std::tuple<float, int>, double>>>::type fifth;
static_assert(std::is_same<fifth, std::tuple<int, std::string, float, int, double>>::value, "Fifth not the same");
} 

编辑:我重写了实现,使其更具可读性和更短(受@DyP启发) 编辑2:解释代码

这是我对此的刺痛。 我试图记录正在发生的事情以使其清楚:

我们从Flatten开始。 它需要一个类型。 我们将在下面专门介绍它:

template<typename T>
struct Flatten;

这是我们的主力军。 获取 Src,并展平其内容并将其附加到 Dest:

template<typename Dest, typename Src>
struct Flatten_append;

空的右侧包装意味着返回左侧:

template<template<typename...>class Pack, typename... LHS>
struct Flatten_append< Pack<LHS...>, Pack<> > {
typedef Pack<LHS...> type;
};

第一个参数是 Pack<...> 的右侧应在处理前展平:

template<template<typename...>class Pack, typename... LHS, typename... RHS0, typename... RHSrest>
struct Flatten_append< Pack<LHS...>, Pack<Pack<RHS0...>, RHSrest... > >:
Flatten_append< Pack<LHS...>, Pack< RHS0..., RHSrest... > >
{};

否则,非空的右侧包应将其第一个元素移动到左侧:(这将比上述匹配弱,因为它不太专业)

template<template<typename...>class Pack, typename... LHS, typename RHS0, typename... RHSrest>
struct Flatten_append< Pack<LHS...>, Pack<RHS0, RHSrest... > >:
Flatten_append< Pack<LHS..., RHS0>, Pack< RHSrest... > >
{};

在Flatten_append到空包方面实现扁平化:

template<template<typename...>class Pack, typename... Ts>
struct Flatten< Pack<Ts...> >:Flatten_append< Pack<>, Pack<Ts...> > {};

目标是尽可能清楚地了解正在发生的事情。

现在,您会注意到此设计的缺点是它会展平任何仅包含类型的template我们可能想传入我们想要扁平化的包。

template<template<typename...>class Pack, typename T>
struct Flatten;
template<template<typename...>class Pack, typename Dest, typename Src>
struct Flatten_append;
template<template<typename...>class Pack, typename... Ts>
struct Flatten< Pack<Ts...> > : Flatten_append< Pack, Pack<>, Pack<Ts...> > {};

然后将Flatten_append< blah, blah, blah >的每个专业更改为Flatten_append< Pack, blah, blah, blah >

这意味着您传入要显式展平的template,而代码仅平展该template

实际上,这可能不需要,因为Pack类型是从传入的左侧类型推断出来的。