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

可變參數(shù)模板

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

使用規(guī)則

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

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

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

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

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

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

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

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

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

對于類模板來說,可變模板參數(shù)包必須是模板參數(shù)列表中的最后一個參數(shù)。但是對于函數(shù)模板來說,則沒有這個限制,考慮下面的情況:

template<typename ... Ts, typename U>
class Invalid
{};   // 這是非法的定義,因為永遠無法推斷出U的類型

template<typename ... Ts, typename U>
void valid(U u, Ts ... args);  // 這是合法的,因為可以推斷出U的類型
// void invalid(Ts ... args, U u); // 非法的,永遠無法推斷出U

valid(1.0, 1, 2, 3); // 此時,U的類型是double,Ts是{int, int, int}

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

無法直接遍歷傳給可變參數(shù)模板的不同參數(shù),但是可以借助遞歸的方式來使用可變參數(shù)模板。可變參數(shù)模板允許創(chuàng)建類型安全的可變長度參數(shù)列表。下面定義一個可變參數(shù)函數(shù)模板processValues(),它允許以類型安全的方式接受不同類型的可變數(shù)目的參數(shù)。函數(shù)processValues()會處理可變參數(shù)列表中的每個值,對每個參數(shù)執(zhí)行對應(yīng)版本的handleValue()。

// 處理每個類型的實際函數(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 ...); // 解包,然后遞歸
}

可以看到這個例子用了三次... 運算符,但是有兩層不同的含義。用在參數(shù)模板列表以及函數(shù)參數(shù)列表,其表示的是參數(shù)包。前面說到,參數(shù)包可以接受任意數(shù)量的參數(shù)。用在函數(shù)實際調(diào)用中的...運算符,它表示參數(shù)包擴展,此時會對args解包,展開各個參數(shù),并用逗號分隔。模板總是至少需要一個參數(shù),通過args...解包可以遞歸調(diào)用processValues(),這樣每次調(diào)用都會至少用到一個模板參數(shù)。對于遞歸來說,需要終止條件,當(dāng)解包后的參數(shù)只有一個時,調(diào)用接收一個參數(shù)模板的processValues()函數(shù),從而終止整個遞歸。

假如對processValues()進行如下調(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ù)會根據(jù)實際類型推導(dǎo)自動調(diào)用正確版本的handleValue()函數(shù),所以這種可變參數(shù)列表是完全類型安全的。如果調(diào)用processValues()函數(shù)帶有的一個參數(shù),無對應(yīng)的handleValue()函數(shù)版本,那么編譯器會產(chǎn)生一個錯誤。

前面的實現(xiàn)有一個致命的缺陷,那就是遞歸調(diào)用時參數(shù)是復(fù)制傳值的,對于有些類型參數(shù),其代價可能會很高。一個高效且合理的方式是按引用傳值,但是對于字面量調(diào)用processValues()這樣會存在問題,因為字面量僅允許傳給const引用參數(shù)。比較幸運的是,我們可以考慮右值引用。使用std::forward()函數(shù)可以實現(xiàn)這樣的處理,當(dāng)把右值引用傳遞給processValues()函數(shù)時,它就傳遞為右值引用,但是如果把左值引用傳遞給processValues()函數(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ù)處理后,再解包,然后遞歸
}

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

這里我們通過可變參數(shù)模板實現(xià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的第一個參數(shù)固定是const char*類型。

References

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容