C++可變參數(shù)模板

可變參數(shù)模板

原文鏈接: http://blog.csdn.net/xiaohu2022/article/details/69076281
普通模板只可以采取固定數(shù)量的模板參數(shù)形葬。然而裳凸,有時(shí)候我們希望模板可以接收任意數(shù)量的模板參數(shù)禀崖,這個(gè)時(shí)候可以采用可變參數(shù)模板施敢。對(duì)于可變參數(shù)模板牲证,其將包含至少一個(gè)模板參數(shù)包,模板參數(shù)包是可以接收0個(gè)或者多個(gè)參數(shù)的模板參數(shù)。相應(yīng)地,存在函數(shù)參數(shù)包入桂,意味著這個(gè)函數(shù)參數(shù)可以接收任意數(shù)量的參數(shù)。

使用規(guī)則

一個(gè)可變參數(shù)類模板定義如下:

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

可以用任意數(shù)量的類型來(lái)實(shí)例化Tuple:

Tuple<> t0;
Tuple<int> t1;
Tuple<int, string> t2;
// Tuple<0> error;  0 is not a type

如果想避免出現(xiàn)用0個(gè)模板參數(shù)來(lái)實(shí)例化可變參數(shù)模板驳阎,可以這樣定義模板:

template<typename T, typename ... Types>
class Tuple
{};

此時(shí)在實(shí)例化時(shí)抗愁,必須傳入至少一個(gè)模板參數(shù)馁蒂,否則無(wú)法編譯。
同樣地蜘腌,可以定義接收任意參數(shù)的可變參數(shù)函數(shù)模板:

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

// 一些合法的調(diào)用
f();
f(1);
f(3.4, "hello");

對(duì)于類模板來(lái)說(shuō)沫屡,可變模板參數(shù)包必須是模板參數(shù)列表中的最后一個(gè)參數(shù)。但是對(duì)于函數(shù)模板來(lái)說(shuō)逢捺,則沒(méi)有這個(gè)限制谁鳍,考慮下面的情況:

template<typename ... Ts, typename U>
class Invalid
{};   // 這是非法的定義,因?yàn)橛肋h(yuǎn)無(wú)法推斷出U的類型

template<typename ... Ts, typename U>
void valid(U u, Ts ... args);  // 這是合法的劫瞳,因?yàn)榭梢酝茢喑鯱的類型
// void invalid(Ts ... args, U u); // 非法的,永遠(yuǎn)無(wú)法推斷出U

valid(1.0, 1, 2, 3); // 此時(shí)绷柒,U的類型是double志于,Ts是{int, int, int}

可變參數(shù)函數(shù)模板實(shí)例

無(wú)法直接遍歷傳給可變參數(shù)模板的不同參數(shù),但是可以借助遞歸的方式來(lái)使用可變參數(shù)模板废睦∷耪溃可變參數(shù)模板允許創(chuàng)建類型安全的可變長(zhǎng)度參數(shù)列表。下面定義一個(gè)可變參數(shù)函數(shù)模板processValues()嗜湃,它允許以類型安全的方式接受不同類型的可變數(shù)目的參數(shù)奈应。函數(shù)processValues()會(huì)處理可變參數(shù)列表中的每個(gè)值,對(duì)每個(gè)參數(shù)執(zhí)行對(duì)應(yīng)版本的handleValue()购披。

// 處理每個(gè)類型的實(shí)際函數(shù)
void handleValue(int value) { cout << "Integer: " << value << endl; }
void handleValue(double value) { cout << "Double: " << value << endl; }
void handleValue(string value) { cout << "String: " << value << endl; }

// 用于終止迭代的基函數(shù)
template<typename T>
void processValues(T arg)
{
    handleValue(arg);
}

// 可變參數(shù)函數(shù)模板
template<typename T, typename ... Ts>
void processValues(T arg, Ts ... args)
{
    handleValue(arg);
    processValues(args ...); // 解包杖挣,然后遞歸
}

可以看到這個(gè)例子用了三次... 運(yùn)算符,但是有兩層不同的含義刚陡。用在參數(shù)模板列表以及函數(shù)參數(shù)列表惩妇,其表示的是參數(shù)包。前面說(shuō)到筐乳,參數(shù)包可以接受任意數(shù)量的參數(shù)歌殃。用在函數(shù)實(shí)際調(diào)用中的...運(yùn)算符,它表示參數(shù)包擴(kuò)展蝙云,此時(shí)會(huì)對(duì)args解包氓皱,展開(kāi)各個(gè)參數(shù),并用逗號(hào)分隔勃刨。模板總是至少需要一個(gè)參數(shù)波材,通過(guò)args...解包可以遞歸調(diào)用processValues(),這樣每次調(diào)用都會(huì)至少用到一個(gè)模板參數(shù)朵你。對(duì)于遞歸來(lái)說(shuō)各聘,需要終止條件,當(dāng)解包后的參數(shù)只有一個(gè)時(shí)抡医,調(diào)用接收一個(gè)參數(shù)模板的processValues()函數(shù)躲因,從而終止整個(gè)遞歸早敬。

假如對(duì)processValues()進(jìn)行如下調(diào)用:

processsValues(1, 2.5, "test");

其產(chǎn)生的遞歸調(diào)用如下:

processsValues(1, 2.5, "test");
    handleValue(1);
    processsValues(2.5, "test");
        handleValue(2.5);
        processsValues("test");
            handleValue("test");

由于processValues()函數(shù)會(huì)根據(jù)實(shí)際類型推導(dǎo)自動(dòng)調(diào)用正確版本的handleValue()函數(shù),所以這種可變參數(shù)列表是完全類型安全的大脉。如果調(diào)用processValues()函數(shù)帶有的一個(gè)參數(shù)搞监,無(wú)對(duì)應(yīng)的handleValue()函數(shù)版本,那么編譯器會(huì)產(chǎn)生一個(gè)錯(cuò)誤镰矿。

前面的實(shí)現(xiàn)有一個(gè)致命的缺陷琐驴,那就是遞歸調(diào)用時(shí)參數(shù)是復(fù)制傳值的,對(duì)于有些類型參數(shù)秤标,其代價(jià)可能會(huì)很高绝淡。一個(gè)高效且合理的方式是按引用傳值,但是對(duì)于字面量調(diào)用processValues()這樣會(huì)存在問(wèn)題苍姜,因?yàn)樽置媪績(jī)H允許傳給const引用參數(shù)牢酵。比較幸運(yùn)的是,我們可以考慮右值引用衙猪。使用std::forward()函數(shù)可以實(shí)現(xiàn)這樣的處理馍乙,當(dāng)把右值引用傳遞給processValues()函數(shù)時(shí),它就傳遞為右值引用垫释,但是如果把左值引用傳遞給processValues()函數(shù)時(shí)丝格,它就傳遞為左值引用。下面是具體實(shí)現(xiàn):

// 用于終止迭代的基函數(shù)
template<typename T>
void processValues(T &&arg)
{
    handleValue(std::forward<T>(arg));
}

// 可變參數(shù)函數(shù)模板
template<typename T, typename ... Ts>
void processValues(T&& arg, Ts&& ... args)
{
    handleValue(std::forward<T>(arg));
    processValues(std::forward<Ts>(args) ...); // 先使用forward函數(shù)處理后棵譬,再解包显蝌,然后遞歸
}

實(shí)現(xiàn)簡(jiǎn)化的printf函數(shù)

這里我們通過(guò)可變參數(shù)模板實(shí)現(xiàn)一個(gè)簡(jiǎn)化版本的printf函數(shù):

// 基函數(shù)
void tprintf(const char* format)
{
    cout << format;
}

template<typename T, typename ... Ts>
void tprintf(const char* format, T&& value, Ts&& ... args)
{
    for (; *format != '\0'; ++format)
    {
        if (*format == '%')
        {
            cout << value;
            tprintf(format + 1, std::forward<Ts>(args) ...); // 遞歸
            return;
        }
        cout << *format;
    }
}
int main()
{

    tprintf("% world% %\n", "Hello", '!', 2017);
    // output: Hello, world! 2017
    cin.ignore(10);
    return 0;
}

其方法基本與processValues()是一致的,但是由于tprintf的第一個(gè)參數(shù)固定是const char*類型茫船。

References

[1] Marc Gregoire. Professional C++, Third Edition, 2016.
[2] cppreference parameter pack

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末琅束,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子算谈,更是在濱河造成了極大的恐慌涩禀,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件然眼,死亡現(xiàn)場(chǎng)離奇詭異艾船,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)高每,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門屿岂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人鲸匿,你說(shuō)我怎么就攤上這事爷怀。” “怎么了带欢?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵运授,是天一觀的道長(zhǎng)烤惊。 經(jīng)常有香客問(wèn)我,道長(zhǎng)吁朦,這世上最難降的妖魔是什么柒室? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮逗宜,結(jié)果婚禮上雄右,老公的妹妹穿的比我還像新娘。我一直安慰自己纺讲,他們只是感情好擂仍,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著刻诊,像睡著了一般防楷。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上则涯,一...
    開(kāi)封第一講書(shū)人閱讀 49,166評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音冲簿,去河邊找鬼粟判。 笑死,一個(gè)胖子當(dāng)著我的面吹牛峦剔,可吹牛的內(nèi)容都是我干的档礁。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼吝沫,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼呻澜!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起惨险,我...
    開(kāi)封第一講書(shū)人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤羹幸,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后辫愉,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體栅受,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年恭朗,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了屏镊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡痰腮,死狀恐怖而芥,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情膀值,我是刑警寧澤棍丐,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布误辑,位于F島的核電站,受9級(jí)特大地震影響骄酗,放射性物質(zhì)發(fā)生泄漏稀余。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一趋翻、第九天 我趴在偏房一處隱蔽的房頂上張望睛琳。 院中可真熱鬧,春花似錦踏烙、人聲如沸师骗。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)辟癌。三九已至,卻和暖如春荐捻,著一層夾襖步出監(jiān)牢的瞬間黍少,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工处面, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留厂置,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓魂角,卻偏偏與公主長(zhǎng)得像昵济,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子野揪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容