C++11中的變長模板的示例詳解
1.C99中的變長函數(shù)
宏函數(shù)可以實現(xiàn)變長:就是采用C99中的變長宏__VA_ARGS__,如下所示為C99代碼:
#include<stdio.h> #define LOG(...){\ fprintf(stderr,"%s: Line %d:\t",__FILE__,__LINE__);\ fprintf(stderr,__VA_ARGS__);\ fprintf(stderr,"\n");\ } int main() { int x=3; LOG("x=%d",x);//D:\study\c++\c++11\__VA_ARGS__.cpp: Line 10: x=3 }
普通函數(shù)也可以實現(xiàn)變長
#include<stdio.h> #include<stdarg.h> ???????double SumOfFloat(int count,...) { va_list ap; double sum=0; va_start(ap,count);//使得ap初始化為參數(shù)列表的handle for(int i=0;i<count;i++) sum+=va_arg(ap,double);//每次讀取sizeof(double)的字節(jié) va_end(ap); return sum; } int main() { printf("%f\n",SumOfFloat(3,1.2f,3.4,5.6));//10.200000 }
注意看上面代碼中,SumOfFloat中的第一個參數(shù)count,這就意味著,你必須告訴函數(shù)它的參數(shù)的具體個數(shù),這就不太方便,例如SumOfFloat(3,1.2f,3.4,5.6)中的第一個參數(shù)3。
2.C++11中的變長函數(shù)
C++11實現(xiàn)上面功能的一種方法是:使用initializer_list作為函數(shù)形參
#include<iostream> #include<initializer_list> using namespace std; double SumOfFloat(const initializer_list<double>& l) { double sum=0; for(auto &val:l) sum+=val; return sum; } int main() { printf("%f\n",SumOfFloat({1.2f,3.4,5.6}));//10.200000 }
這種思路感覺有點投機取巧,不太好
另一種方法是使用變長模板
#include<iostream> template<typename... T>double SumOfFloat(T...);//模板函數(shù)聲明 template<typename ...Args>//遞歸定義 double SumOfFloat(double a,Args... args) { return a+SumOfFloat(args...); } double SumOfFloat(double a){return a;}//邊界條件 int main() { printf("%f\n",SumOfFloat(1.2f,3.4,5.6));//10.200000 }
先不對上述代碼做解釋,只要知道<typename... T>中的T叫做模板參數(shù)包,它是類型的,是一種特殊的類型,它可以被推導為多個類型的pack。即SumOfFloat(1.2f,3.4,5.6)會將T推導為double,double,double的一種pack類型。
除此之外,必須知道這種設計必須是遞歸定義的。
3.詳解變長模板
首先講一個前置概念:SFINAE.
3.1 更一般的SFINAE規(guī)則
在C++98中,我們就有SFINAE法則:Substitution failure is not an error
即匹配失敗不算失敗,在C++中就是,即使模板展開失敗,也不會報錯
struct Test { typedef int FOO; }; template<typename T> void foo(typename T::FOO){} template<typename T> void foo(T){} int main() { foo<Test>(10); foo<int>(10); }
上面代碼在C++98中也可以通過編譯,上面foo(typename T::FOO)中typename顯式的表示,T::FOO是一個類型,在foo<int>(10);中,編譯器會嘗試用 第一個模板函數(shù)來匹配它,但是會發(fā)現(xiàn)int::FOO錯誤,但是編譯器不會報錯,這就是SFINAE
在C++11中對SFINAE規(guī)則做了放松,
template<int I> struct A {}; char xxx(int); char xxx(float); template<typename T>A<sizeof(xxx((T)0))> f(T){} int main() { f(1); }
有一些C++98編譯器會對上式報錯,這是因為它們認為模板參數(shù)過于復雜,即這里的sizeof(xxx((T)0)),不過現(xiàn)在的編譯器都狠優(yōu)秀,它們可以完成這種表達式的推導,C++11的標準是:只要表達式中沒有出現(xiàn)外部于表達式本身的元素,編譯器都可以完成推導
3.2 模板參數(shù)包的概念
接下來的內(nèi)容會有一定難度。
我們知道,在C++98中模板參數(shù)有3種:類型的,非類型的,模板類型的。
template<typename T,int i,template<typename> class A>中T就是類型的,i是非類型的,A是模板類型的。
在C++11中,我們?yōu)榱酥С肿冮L的模板,我們加入一種新的模板參數(shù):模板參數(shù)包。
所以在C++11中模板參數(shù)有4種:類型的,非類型的,模板類型的和模板參數(shù)包。
而模板參數(shù)包又可以細分為3種:類型的模板參數(shù)包,非類型的模板參數(shù)包,模板類型的模板參數(shù)包。
模板參數(shù)包是一種pack,下面我們從模板推導角度來解釋這種pack:
1.(類型的)模板參數(shù)包
template <typename T1,typename T2>class B{}; template <typename... A>class Template: private B<A...>{}; Template<X,Y> xy;
上面中,<typename...A>中A是 (類型的)模板參數(shù)包,它可以接收任意多個類型參數(shù)作為模板參數(shù),具體來說,Template<X,Y>會將A推導為X和Y類型的pack。
B<A...>中A...是一種包擴展,它是模板參數(shù)包unpack的結(jié)果。由于A被推導為X和Y的pack,所以A...就被具體解釋為X,Y,然后具體化為B<X,Y>。
如果我們使用Template<X,Y,Z> xyz就會引發(fā)推導錯誤,沒有任何一個模板適配,這是因為此時A...被解釋為3個類型:X,Y,Z,它無法和B匹配。
2.(非類型的)模板參數(shù)包
template<int i,long j,unsigned int k>class B{}; template<int ...A> struct Pack: private B<A...>{}; Pack<1,0,2> data;
<int ... A>中A是 (非類型的)模板參數(shù)包,它可以接收分離多個非類型參數(shù)作為模板參數(shù),具體來說,Pack<1,0,2>會將A推導為整值1,0,2的pack,而B<A...>中A...是一種包擴展,由于A推導為整值1,0,2的pack,所以A...被具體解釋為1,0,2,然后具體化為B<1,0,2>
3.(模板類型的)模板參數(shù)包
template <typename T> class A; template <typename T> class B; template<template<typename> class T1,template<typename> class T2> class C{}; template<template<typename> class ...T> struct Pack:private C<T...>{}; Pack<A,B> data;
<template<typename> class ...T>中T是 (模板類型的)模板參數(shù)包,它可以接收多個模板作為模板參數(shù),具體來說,Pack<A,B>會將T推導為A和B的pack,而C<T...>中T...就是一種包擴展,由于T推導為A和B的pack,所以T...就被具體解釋為A,B,然后具體化為C<A,B>
3.3 三個簡單的例子
變長模板必須采用遞歸設計,下面是3個簡單的例子,請仔細閱讀。
1.(類型的)模板參數(shù)包的使用
下面給出C++11中tuple的簡單實現(xiàn),
template<typename... Elements> class tuple;//模板聲明 template <typename Head,typename... Tail>//遞歸定義 class tuple<Head,Tail...>:private tuple<Tail...> { Head head; }; template<> class tuple<>{};//邊界條件
同樣也是遞歸定義,這種遞歸的設計就是變長模板最晦澀的地方。
當實例化tuple<double,int,char,float>類時,
第一次:Head被推導為double,Tail...被推導為int,char,float
第二次:Head被推導為int,Tail...被推導為char,float
第三次:Head被推導為char,Tail...被推導為float
第三次:Head被推導為float,Tail...被推導為空
最后由class tuple<>進行遞歸構(gòu)造出模板
2.(非類型的)模板參數(shù)包的使用
#include<iostream> using namespace std; template<long... nums> struct Multiply;//模板聲明 template<long first,long... last>//遞歸定義 struct Multiply<first,last...> { static const long val=first*Multiply<last...>::val; }; template <>//邊界條件 struct Multiply<> { static const long val=1; }; int main() { cout<<Multiply<2,3,4,5>::val<<endl; cout<<Multiply<22,44,66,88,9>::val<<endl; }
上面這種編程方式,叫做模板元編程,他將乘法的計算放到模板推導過程中,就是把計算過程放在編譯階段,這樣運行時就不需要計算了
3.(模板類型的)模板參數(shù)包的使用
template <typename T> class Module1{}; template <typename T> class Module2{}; 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; Container<I,B...> b; }; template<typename I> struct Container<I>{};//邊界條件 int main() { Container<int,Module1,Module2> a; }
3.4 函數(shù)參數(shù)包
函數(shù)參數(shù)包是在寫變長模板函數(shù)中的一個概念
函數(shù)參數(shù)包也是一種pack型變量,它也存在unpack,包擴展的概念。
void g(int,char,double); template<typename ... T> void f(T... args) { g(args...); } f(1,'c',1.2);
在<typename ... T>中的T是 (類型的)模板參數(shù)包 。
在f(T... args)中T...叫做 包擴展
在f(T... args)中的args是一種類型為T...的變量,它叫函數(shù)參數(shù)包
在g(args...)中的args...也是一種包擴展,它是將argsunpack后的產(chǎn)物
例如,這里f(1,'c',1.2)就會將T推導為int,char,double的pack,于是T...就被具體解釋為int,char,double,然后args就是類型為T...的一種變量,args的值是1,'c',1.2的pack,則我們可以在f中調(diào)用g(args...)完成對args的unpack。
下面看一個,C++11中提案的prinf()函數(shù)的實現(xiàn)
#include<iostream> #include<stdexcept> using namespace std; void Printf(const char*s)//邊界條件 { while(*s) { if(*s=='%' && *++s!='%')//確保`%%`不出現(xiàn) throw runtime_error("invalid format string: missing arguments"); cout<<*s++; } } template<typename T,typename ...Args>//遞歸定義 void Printf(const char*s,T value,Args... args) { while(*s) { if(*s=='%' && *++s!='%')//確保`%%`不出現(xiàn) { cout<<value; return Printf(++s,args...); } cout<<*s++; } throw runtime_error("extra arguments provided to Printf"); } int main() { Printf("hello %s\n",(string)"world"); }
變長模板的難點在于我們不知道參數(shù)的個數(shù),我們必須采用遞歸定義,就像上面的Printf就是遞歸定義的,采用的是數(shù)學歸納法,可以細細品味一下上面那段代碼。
3.5 包擴展的進階
...符號可以放在意想不到的地方,例如:
template<typename... A> class T:private B<A>...{};//#1 template<typename... A> class T:private B<A...>{};//#2
對于實例化T<X,Y>,#1會被解釋為
class T<X,Y> class T:private B<X>,private B<Y>{};
#2會被解釋為
class T<X,Y> class T:private B<X,Y>{};
看一下下面這些例子:
#include<iostream> using namespace std; template<typename... T> void DummyWrapper(T... t){}; template<typename T> T pr(T t){ cout<<t; return t; } template<typename... A> void VTPrint(A... a) { DummyWrapper(pr(a)...); } int main() { VTPrint(1,", ",1.2,", abc\n"); }
上面這段代碼,某些編譯器(例如g++)的結(jié)果是逆序的:
, abc
1.2, 1
應該是,不同的編譯器可能包擴展的順序不太一樣,有些的逆序的。
下面我們看一段晦澀難懂的代碼
#include<iostream> #include<tuple> using namespace std; template<typename A,typename B> struct S { int a=1; }; template< template<typename...> class T, typename... TArgs, template<typename...> class U, typename... UArgs > struct S< T<TArgs...> , U<UArgs...> >{int a=2;}; int main() { S<int,float> p; S<tuple<int,char>,tuple<float>> s; //S<tuple,int,char,tuple,float> s;編譯出錯 cout<<s.a<<endl;//2 }
注意上面這段代碼中,最終輸出是2,奇怪的地方在于,S<tuple<int,char>,tuple<float>>如何匹配第二個模板呢?這種設計是約定俗稱的,沒有任何原因,記住上面這種巧妙的設計就行了。
3.6 sizeof...()的使用
sizeof...()其實狠簡單的,它就是獲得pack中的變量的個數(shù),有些用的
#include<cassert> #include<iostream> using namespace std; template<typename... A> void Print(A... arg) { assert(false); } void Print(int a1,int a2,int a3,int a4,int a5,int a6) { cout<<a1<<", "<<a2<<", "<<a3<<", "<<a4<<", "<<a5<<", "<<a6<<endl; } template<class... A>int Vaargs(A... args) { int size=sizeof...(args);//或者sizeof...(A) switch (size) { case 0:Print(99,99,99,99,99,99); break; case 1:Print(99,99,args...,99,99,99); break; case 2:Print(99,99,args...,99,99); break; case 3:Print(args...,99,99,99); break; case 4:Print(99,args...,99); break; case 5:Print(99,args...); break; case 6:Print(args...); break; default: Print(0,0,0,0,0,0); } } int main() { Vaargs();//99, 99, 99, 99, 99, 99 Vaargs(1);//99, 99, 1, 99, 99, 99 Vaargs(1,2);//99, 99, 1, 2, 99, 99 Vaargs(1,2,3);//1, 2, 3, 99, 99, 99 Vaargs(1,2,3,4);//99, 1, 2, 3, 4, 99 Vaargs(1,2,3,4,5);//99, 1, 2, 3, 4, 5 Vaargs(1,2,3,4,5,6);//1, 2, 3, 4, 5, 6 Vaargs(1,2,3,4,5,6,7);//0, 0, 0, 0, 0, 0 }
3.7 變長模板和完美轉(zhuǎn)發(fā)的配合
#include<iostream> using namespace std; struct A { A(){}; A(const A&a){cout<<"Copy Constructed "<<__func__<<endl;} A(A&& a){cout<<"Move Constructed "<<__func__<<endl;} }; struct B { B(){}; B(const B&b){cout<<"Copy Constructed "<<__func__<<endl;} B(B&& b){cout<<"Move Constructed "<<__func__<<endl;} }; template<typename... T> struct MultiTypes;//模板聲明 template<typename T1,typename... T>//遞歸定義 struct MultiTypes<T1,T...>: public MultiTypes<T...> { T1 t1; MultiTypes<T1,T...>(T1 a,T... b):t1(a),MultiTypes<T...>(b...) { cout<<"MultiTypes<T1,T...>(T1 a,T... b)"<<endl; } }; template<> struct MultiTypes<>//邊界條件 { MultiTypes<>(){cout<<"MultiTypes<>()"<<endl;} }; template<template<typename...> class VariadicType,typename... Args> VariadicType<Args...> Build(Args&& ... args) { return VariadicType<Args...>(std::forward<Args>(args)...); } int main() { A a; B b; Build<MultiTypes>(a,b); //等價于Build<MultiTypes,A,B>(a,b); }
MultiTypes<>()
MultiTypes<T1,T...>(T1 a,T... b)
MultiTypes<T1,T...>(T1 a,T... b)
沒啥好說的,這就是完美轉(zhuǎn)發(fā),它根部就不調(diào)用移動構(gòu)造和拷貝構(gòu)造函數(shù),完全都是靠引用傳遞的
以上就是C++11中的變長模板的示例詳解的詳細內(nèi)容,更多關(guān)于C++11變長模板的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Qt實現(xiàn)SqlTableModel映射組件應用小結(jié)
在Qt中提供了QSqlTableModel模型類,它為開發(fā)者提供了一種直觀的方式來與數(shù)據(jù)庫表格進行交互,本文就來介紹一下Qt實現(xiàn)SqlTableModel映射組件應用小結(jié),感興趣的可以了解一下2023-12-12C++如何將二叉搜索樹轉(zhuǎn)換成雙向循環(huán)鏈表(雙指針或數(shù)組)
這篇文章主要介紹了C++如何將二叉搜索樹轉(zhuǎn)換成雙向循環(huán)鏈表(雙指針或數(shù)組),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-05-05