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ō),則沒有這個(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解包,展開各個(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閱讀 227,224評(píng)論 6 529
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 97,916評(píng)論 3 413
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,014評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,466評(píng)論 1 308
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,245評(píng)論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,795評(píng)論 1 320
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,869評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,010評(píng)論 0 285
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,524評(píng)論 1 331
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,487評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,634評(píng)論 1 366
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,173評(píng)論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 43,884評(píng)論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,282評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,541評(píng)論 1 281
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,236評(píng)論 3 388
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,623評(píng)論 2 370

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