可變長模板

目前大部分主流編譯器的最新版本均支持了C++11標準(官方名爲ISO/IEC14882:2011)大部分的語法特性,其中比較難理解的新語法特性可能要屬變長參數模板(variadic template)了。下面先介紹一下這個語法特性在C++11標準中的描述。ios

14.5.3變長參數模板
一、一個模板形參包(template parameter pack)是一個接受零個或多個模板實參的模板形參。【例:express

template<class ... Types> struct Tuple { };

Tuple<> t0; // Types不含任何實參
Tuple<int> t1; // Types含有一個實參:int
Tuple<int, float> t2; // Types含有兩個實參:int和float
Tuple<0> error; // 錯誤:0不是一個類型
——例結束】ide


二、一個函數形參包(function parameter pack)是一個接受零個或多個函數實參的函數形參。【例:函數

template<class ... Types> void f(Types... args);

f(); // OK:args不含有任何實參
f(1); // OK:args含有一個實參:int
f(2, 1.0); // OK:args含有兩個實參int和double
——例結束】spa


三、一個形參包要麼是一個模板形參包,要麼是一個函數形參包。rest


四、一個包擴展(expansion)由一個模式(pattern)和一個省略號組成。包擴展的實例中一個列表中產生零個或多個模式的實例。模式的形式依賴於擴展所發生的上下文中。【譯者注:ip

template <typename... TS> // typename... TS爲模板形參包,TS爲模式
static void MyPrint(const char* s, TS... args) // TS... args爲函數形參包,args爲模式
{
printf(s, args...);
}
ci

包擴展會在如下上下文中發生:編譯器

——在一個函數形參包中(8.3.5);該模式是一個沒有省略號的parameter-declaration。【譯者注:it

template <typename... Types>
void func(Types... args); // args爲模式

——在一個模板形參包中,該包是一個包擴展(14.1):

——若是模板形參包是一個parameter-declaration;且該模式是沒有省略號的parameter-declaration。【譯者注:

template <typename... Types> // Types爲模式
void func(Types... args);

——若是模板形參包是具備一個template-parameter-list的一個type-parameter;且該模式是相應的type-parameter且沒有省略號。【譯者注:

// 這裏模板形參包的模式爲Classes
template <template <typename P, typename Q> class ... Classes>
struct MyAStruct;

——在一個初始化器列表中(8.5);模式是一個initializer-clause。

——在一個base-specifier-list(條款10)中;模式是一個base-specifier。

——在一個mem-initializer-list(12.6.2)中;模式是一個mem-initializer。

——在一個template-argument-list(14.3)中,模式是一個template-argument。

——在一個dynamic-exception-specification(15.4)中;模式是type-id。

——在一個attribute-list中(7.6.1);模式是一個attribute。

——在一個alignment-specifier(7.6.2)中;模式是沒有省略號的alignment-specifier。

——在一個capture-list(5.1.2)中,模式是一個capture。

——在一個sizeof...表達式(5.3.3)中,模式是一個identifier。

【例:

template<class ... Types> void f(Types ... rest);
template<class ... Types> void g(Types ... rest) {
f(&rest ...); // 「&rest ...」是一個包擴展;「&rest」是其模式
}
——例結束】


五、一個形參包,其名字出如今一個包擴展的模式以內,被其包擴展而擴展。一個形參包的名字的一次出現僅僅被最內部所封閉的包擴展而擴展。一個包擴展模式應該命名一個或多個形參包,一個嵌套的包擴展不會擴展它們;這樣的形參被稱爲模式中不被擴展的形參包。全部被一個包擴展所擴展的形參包應該具備相同數量的所指定的實參。沒有被擴展的一個形參包的一個名字的一次出現是不良形式的。【例:

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;
}; // 譯者注:這裏是對Pair<Args1, Args2>進行擴展
};

// T1是Tuple<Pair<short, unsignd short>, Pair<int, unsigned> >
typedef zip<short, int>::with<unsigned short, unsigned>::type T1;

// 錯誤:對Args1和Args2指定了不一樣個數的實參
typedef zip<short>::with<unsigned short, unsigned>::type t2;

template <typename ... Args>
void f(Args... args)
{

}

template<class ... Args>
void g(Args ... args) { // OK:Args被函數形參包args擴展
f(const_cast<const Args*>(&args)...); // OK:「Args」與「args」被擴展
f(5 ...); // 錯誤:模式沒包含任何形參包
f(args); // 錯誤:形參包「args」沒被擴展
f(h(args ...) + args ...); // OK:第一個「args」在h內被擴展,第二個「args」在f內被擴展
}
——例結束】


六、一個包擴展的實例,它不是一個sizeof...表達式,產生一個列表E1,E2,E3,...,EN,這裏,N是包擴展形參中元素的個數。每一個Ei經過實例化該模式並用其第i個元素來代替每一個包擴展形參來生成。全部Ei變爲封閉列表中的元素。【注:列表的多樣性會根據上下文而有所不一樣:expression-list,base-specifier-list,template-argument-list,等等。——注結束】當N爲零時,擴展的實例產生一個空列表。這樣的一個實例並不改變封閉構造的語法上的解釋,甚至在忽略整個列表會致使不良形式的狀況下或會在語法上產生奇異性的狀況下。【例:

template<class... T>
struct X : T...
{
// 譯者添加
X(T... args) { }
};

template<class... T> void f(T... values) {
X<T...> x(values...);
}

template void f<>(); // OK:X<>沒有基類;x是類型X<>被值初始化的一個變量

// 譯者添加:
int main() {
struct Y { };
struct Z { };
f<>(); // 使用template void f<>();其中使用X<> x();

// 使用template<class... T> void f(T... values);
// 其內部使用X<Y, Z> x(Y(), Z());
// 而X<Y, Z>的定義爲:struct X : Y, Z { X(Y arg1, Z arg2) { } };
f(Y(), Z());
}
——例結束】


七、一個sizeof...表達式的實例(5.3.3)產生了包含在它所擴展的形參包中元素個數的一個整數常量。


上述就是C++11標準對變長模板形參的描述。下面我將給出一些代碼示例來作進一步的描述幫助你們更好地去理解,尤爲是包擴展機制。

複製代碼
//============================================================================
// Name : CPPTest.cpp
// Author : Zenny Chen
// Version :
// Copyright : Your copyright notice
// Description : Hello World in C++, Ansi-style
//============================================================================

#include <iostream>
#include <typeinfo>
using namespace std;
#include <stdio.h>
#include <stdarg.h>

struct MyTest;

// 普通的C函數變長形參
static void MyCPrint(const char *s, ...)
{
char strBuffer[1024];
va_list ap;
va_start(ap, s);
vsprintf(strBuffer, s, ap);
va_end(ap);
printf(strBuffer);
}

template <typename... TS> // typename... TS爲模板形參包,TS爲模式
static int MyPrint(const char* s, TS... args) // TS... args爲函數形參包,args爲模式
{
return printf(s, args...);
}

template <typename... TS> // 模板形參包(template parameter pack)
static void DummyIter(TS... args) // 函數形參包(function parameter pack)
{
}

template <typename T>
static T Show(T t, int n)
{
cout << "The value is: " << t << ", and n = " << n << endl;
return t;
}

template <typename... TS>
static void Func(TS... args)
{
// 這裏,Show(args, sizeof...(args))爲模式,所以Show(args, sizeof...(args))...被擴展
// 每一個args實例的類型爲其所對應TS模板實參的類型
// 這裏,Show(T, int)函數必須返回T類型,不能是void,因爲void與TS...類型沒法匹配
DummyIter(Show(args, sizeof...(args))...);
}

// 請你們注意一下如下兩種函數調用方式的不一樣!
template <typename... Types>
static void Foo(Types... args)
{
// 對DummyIter調用擴展MyPrint("The type is: %s\n", typeid(args).name())
DummyIter(MyPrint("The type is: %s\n", typeid(args).name()) ...);
puts("============");
// 對MyPrint調用擴展args
DummyIter(MyPrint("The first value is: %d, second is: %s, third is: %f\n", args...));
}

// 對C++11標準14.5.3條款中的第5項中例子的進一步描述
template <typename... Types>
struct VariadicStruct : Types...
{

};

template <typename... Types>
static void ConstructStruct(void)
{
VariadicStruct<Types...>();
}

template void ConstructStruct<>(void); // OK:VariadicStruct<>沒有基類


template <typename... Types>
static void f(Types... args)
{
printf("The sample values are: %f, %f\n", args...);
}

// 特化不帶任何參數的f
template<> void f<>()
{
cout << "No arguments!" << endl;
}

template <typename T1, typename T2>
static auto h(T1 t1, T2 t2) -> decltype(t1 * t2)
{
return t1 * t2;
}

template <typename... Types>
static void g(Types... args)
{
// 這裏,調用main函數中的g(10, 0.1)以後,會被展開爲:
// f(h(10, 0.1) + 10, h(10, 0.1) + 0.1);
// 這裏有兩層包展開,首先對於f(),其模式爲h(args...) + args
// 而後對於h(),其模式爲args
// 所以,最右邊的省略號實際上是對整個(h(args...) + args)進行擴展
// 其等價於:f((h(args...) + args) ...);
f(h(args...) + args ...);
}

extern "C" void cppTest(void){ MyCPrint("This is C print: %d, %s\n", 1, "Hello, world!"); MyPrint("This is my print: %d, %s\n", -1, "Hello, world!"); Func(-100, 0.5); puts(""); Foo(3, "Hello", 0.25); // 對C++11標準14.5.3條款中的第5項中例子的進一步描述 puts("\n"); struct A{}; struct B{}; ConstructStruct<A, B>(); // 在此函數內部構造了VariadicStruct<A, B> ConstructStruct<>(); // 在此函數內構造了VariadicStruct<>,它沒有基類 g(10, 0.1); g<>();}

相關文章
相關標籤/搜索