浅墨浓香

想要天亮进城,就得天黑赶路。

导航

第22课 可变参数模板(3)_深入理解参数包

Posted on 2017-11-11 13:56  浅墨浓香  阅读(1882)  评论(0编辑  收藏  举报

1. 模板参数包

(1)模板参数包的类型

  ①类型参数包(Type parameter packs):如template<typname… T> class tuple;

  ②非类型参数包(Non-type parameter packs):如template<int…A> class Test{}; Test<1,0,2> test;

  ③模板模板参数包(Template template parameter packs):见后面的例子

(2)使用注意事项

  ①在模板参数可被推导的情况下(如函数模板和类模板的偏特化),可在模板参数列表中声明多个模板参数包

  ②但如果无法推导出模板参数,则模板参数列表中最多只能声明一个模板参数包而且这个参数包必须是模板中的最后一个模板参数

  ③默认参数不能用于模板参数包。如template<typename...T=int> struct foo1{};

(3)模板模板参数包

template<typename I, template<typename> class... B> struct Container{};
template<typename I, template<typename> class A, template<typename> class... B>
struct Container<I, A, B...>
{
    A<I> a; //A本身是一个模板
    Container<I, B...> b; //B...为模板模板参数包,注意声明为template<typename> class... B>
};

template<typename I> struct Container<I>{};

【编程实验】完美转发参数包

#include <iostream>
using namespace std;

struct A
{
    A(){cout << "Constructed " << __func__ << endl;}
    A(const A& a){cout << "Copy Constructed " << __func__ << endl;}
    A(A&& a) {cout << "Move Constructed " << __func__ << endl;}
};

struct B
{
    B(){cout << "Constructed " << __func__ << endl;}
    B(const B& a){cout << "Copy Constructed " << __func__ << endl;}
    B(B&& a) {cout << "Move Constructed " << __func__ << endl;}
};

//可变参数模板的定义
template<typename ...T> struct Tuple;
template<typename T1, typename...T>
struct Tuple<T1, T...> : public Tuple<T...>
{
    typename std::decay<T1>::type t1; //T1可能带有cv和引用属性,去除它们
                                      //注意,如果直接定义T1 t1,则可能t1是个引用
    Tuple<T1, T...>(T1 a, T...b):t1(a), Tuple<T...>(b...)
    {
        cout << "Tuple<T1, T...>(T1 a, T... b): ";
        cout << "T1 is reference: " << std::is_reference<T1>::value<< endl;
    }
};

template<>
struct Tuple<>
{
    Tuple(){cout << "Tuple<>" << endl;}
};

//完美转发参数包
template<template<typename...> class Tp, typename...Args>
Tp<Args...> Build(Args&&... args)
{
    return Tp<Args...>(std::forward<Args>(args)...);
}

int main()
{
    A a;
    B b;
    
    Build<Tuple>(a, b);
    
    return 0;
}
/*输出结果
e:\Study\C++11\22>g++ -std=c++11 test2.cpp -fno-elide-constructors
e:\Study\C++11\22>a.exe
Constructed A
Constructed B
Tuple<>
Copy Constructed B
Tuple<T1, T...>(T1 a, T... b): T1 is reference: 1
Copy Constructed A
Tuple<T1, T...>(T1 a, T... b): T1 is reference: 1
Move Constructed B  //以下两个移动构造的调用是由Build函数返回值引起的
Move Constructed A
*/

2. 函数参数包

(1)使用注意事项

  ①函数参数包分为两种:当参数包为最后一个模板参数,被称为trailing函数参数包,否则为non-trailing函数参数包。

  ②函数模板可以带trailing和non-trailing的函数参数包。

  ③non-trailing函数参数包只有在调用时显式指定参数类型时才能被推导出来。如果没有显式指定参数,则non-trailing参数包则会被推导为空

【实例分析】参数包的推导

//1. 类型参数包
template<class... T> class X{};

X<> a;    //空的参数列表
X<int> b; //一个参数
X<int, char, float> c; //3个参数

//2. 非类型参数包
template<bool...A> class X{}; //bool为非类型参数
X<> a;    //空的参数列表
X<true> b; //一个参数
X<true, false, true> c; //3个参数

//3. 多个模板参数包
//3.1 参数包不能被推导
template<class...A, class...B> struct Container1{};
template<class...A, class B> struct Container2{};
template<class...A, class...B, class C> struct Container3{};

Container1<int, float, double> c1; //error,A和B无法被推导出来
Container2<int, float, double> c2; //error, 推导时会认为参数包A为int,float,double
                                   //但B却无指定类型,所以报错。要求将参数包放最后面。
                                   
Container3<int, float, double> c3; //error, A和B无法被推导

template<class A, class...B> struct Container4{};
Container4<int, float, double> c4; //A为int, B为float,double

//3.2 多个参数包(可以被推导出来)
template<typename T1, typename T2> struct foo{};
template<typename... T> struct bar{}; //泛化

template<typename... T1, typename... T2>  //特化
struct bar<foo<T1, T2>...>{}; //ok

template<typename A, typename B>struct S{};

template<
    template<typename...> class T, typename... TArgs,
    template<typename...> class U, typename... UArgs
> struct S< T<TArgs...>, U<UArgs...> > {};

S<int, float> p; //匹配泛化模板
    
//其中的T和U推导是根据tuple的定义进行的,直至推导到边界条件(T和U两个
//tuple都不再包含模板参数),即template<typename A, typename B> S{}为止。
S<std::tuple<int, char>, std::tuple<float>> s; 

//3.3 多个参数包
struct a1 {}; struct a2 {}; struct a3 {}; struct a4 {}; struct a5 {};
template<class...X> struct baseC{};
template<> struct baseC<>{};

template<class...A1> struct container{container(){cout << "container<A1...>" <<endl;}};

//特化版:注意ver2,ver3,ver4将无法被匹配。
template<class...A, class...B, class...C>   //ver 1:baseC中至少2个参数A,B
struct container<baseC<A,B,C...>...>{container(){cout <<"baseC<A,B,C...>..." <<endl;}};

template<class...A, class...B, class...C>   //ver 2:baseC中由于参数包“B...”会“吃掉”所有参数,所以C无法推导
//下面的声明尽管B...位于C的前面,但由于C本身为参数包类型,可能包含0个参数,所以这样的声明又符合语法规则。
struct container<baseC<A,B...,C>...>{container(){cout <<"baseC<A,B...,C>..." <<endl;}};

template<class...A, class...B, class...C>   //ver 3:baseC中A参数包会“吃掉”所有参数,所以B、C无法推导
struct container<baseC<A...,B,C>...>{container(){cout <<"baseC<A...,B,C>..." <<endl;}};

template<class...A, class...B, class...C>   //ver 4:baseC中A参数包会“吃掉”所有参数,所以B、C无法推导
struct container<baseC<A...,B,C...>...>{container(){cout <<"baseC<A...,B,C...>..." <<endl;}};

container<baseC<a1, a2, a5, a5, a5>, baseC<a1, a1, a5, a5, a5>,
          baseC<a1, a1, a5, a5, a5>, baseC<a1, a1, a5, a5, a5> > test1;    

//4. 参数包不能带默认值
template<typename...T=int> struct foo1{}; //error

//1. 函数参数包
template<class...A>  void func(A...args){};
func();    //空参数列表
func(1);   //带1个参数
func(1,2,3,4,5); //带多个同类型的参数
func(1,'x',aWidget); //带多个不同类型的参数

//2. 多参数包的函数模板
template<class...A, class...B>
void func(A...arg1,int sz1, int sz2, B...args)
{
    assert(sizeof...(arg1) == sz1);
    assert(sizeof...(arg2) == sz2);
}

//arg1为non-trailing类型必须显式指定,得A:(int,int,int) B:(int,int,int,int,int)
func<int,int,int>(1,2,3,3,5,1,2,3,4,5) //arg1为non-trailing类型,必须显式指定类型如<int,int,int>
func(0,5,1,2,3,4,5);//A为non-trailing,但此处没有显式,所以为空,B(int,int,int,int,int)

3. 包扩展

(1)参数包可展开的位置

  ①表达式    ②初始化列表    ③基类描述列表    ④类成员初始化列表    ⑤模板参数列表    ⑥异常规格说明列表    ⑦lambda函数的捕获列表

【实例分析】参数包可展开的位置

//1. 出现在表达式中
template<class...A> void func1(A...args){
   cout << sizeof...(args) << endl;
}

template<class...A> int func(A...args){
    func1(99,99,args...,99,99);  //args...出现在函数参数列表的表达式中
    func1(args...,99);
    func1(99,args...);
    func1(args...);
}

//1. 包扩展出现在初始化列表中
template<class ...A>
void func(A... args)
{
    const int size = sizeof...(args+5);
    int res[size] = {99, 98, args...,97,96,95};//args...出现在初始化列表中
}

//2. 包扩展出现基类描述列表和类成员初始化列表中
template<class ...A>
struct Container : public Base<A>... //出现在基类描述列表中,相当于多重继承Base<A1>,Base<A2>,...
{
    Container():Base<A>(12)...{}; //出现在类成员初始化列中表,等价于Base<A1>(12),Base<A2>(12),...
};

//3. 包扩展出现在模板参数列表中
template<typename... I>
struct container
{
    container()
    {
        int array[sizeof...(I)] = {I()...};//出现在初始化列表中
    }
};

template <class A, class B, class...C>
void func(A arg1, B arg2, C... arg3)
{
    container<A, B, C...> t1; //出现在模板参数列表中
    container<C..., A, B> t2; //ok    
}

//4. 包扩展出现在异常规格说明中
template<class ...X>
void func(int arg) throw(X...) //出现在异常规格说明中,相当于throw(X1, X2,...)
{
    
}

//5. 包扩展出现在lambda的捕获列表中
template<class ...Args>
void f(Args... args) {
    auto lm = [&, args...] { return g(args...); };
    lm();
}

(2)注意事项

  ①参数包必须通过包扩展表达式来扩展开来。单纯地使用未被扩展的包名是错误的。如:template<class…B> struct container<B>{}; //B未被扩展,应写成B…。注意container也应该是个可变参数模板。

  ②非参数包,不能用在包扩展表中。如

template<class A, class…B>

void func1(container<A>…args){}; //A不是参数包,不能用在包扩展表达式中。

  ③如果在同一个包扩展表达式中引用了多个参数包,则他们的扩展是同步的,这些参数包必须拥有相同的长度。

//同一个包扩展表达式中,存在多个参数包
template<typename...> struct Tuple{};
template<typename T1, typename T2> struct Pair{};

template<class...Args1> struct zip
{
    template<class...Args2> struct with
    {
        typedef Tuple<Pair<Args1, Args2>...> type; //Args1和Args2在同一个包扩展表达式中
    };
};

//Pair<Arg1, Args2>...展开为Pair<short, unsigned short>, Pair<int, unsigned int> 
typedef zip<short, int> ::with<unsigned short, unsigned int>::type T1;

//由于Arg1和Args2在同一个包扩展表达式(Pair<Arg1, Args2>...)中,所以展开后参数的数量
//应该一致。
//typedef zip<short>::with<unsigned short, unsigned int>::type T2; //error,Args1和Args2参数数量不同

  ④如果包扩展嵌套在另一个包扩展中,则出现在最内层包扩展内的参数包先被扩展。

//包扩展表达式的嵌套
template<class...Args>
void f(Args...args){}

template<class...Args>
void g(Args...args)
{
    f(const_cast<const Args*>(&args)...);//Args和args同步被展开,等价于
    //f(const_cast<const Args1*>(&args1),const_cast<const Args2*>(&args2),...);
    
    //嵌套的包扩展:
    //内部包扩展args...先展开:得h(E1,E2,E3) + args...
    //再展开外部的args...,得h(E1,E2,E3)+E1, h(E1,E2,E3)+E2,h(E1,E2,E3)+E3
    f(h(args...) + args...); 
}