模板:從基礎(chǔ)語(yǔ)法到完美轉(zhuǎn)發(fā)與元組實(shí)現(xiàn))
1. 項(xiàng)目概述為什么我們需要可變參數(shù)模板在C98/03的時(shí)代如果你需要寫一個(gè)能處理任意數(shù)量參數(shù)的函數(shù)比如一個(gè)打印函數(shù)print你可能會(huì)感到非常頭疼。你不得不為不同數(shù)量的參數(shù)編寫多個(gè)重載版本print(int)、print(int, int)、print(int, int, int)……這不僅代碼冗余而且總有上限三個(gè)參數(shù)、五個(gè)參數(shù)或許還能忍要是十個(gè)、二十個(gè)呢維護(hù)起來(lái)簡(jiǎn)直就是噩夢(mèng)。這種對(duì)“可變”參數(shù)的需求在泛型編程中尤其強(qiáng)烈比如我們想創(chuàng)建一個(gè)能容納任意類型、任意數(shù)量元素的元組tuple或者一個(gè)能完美轉(zhuǎn)發(fā)任意參數(shù)給其他函數(shù)的包裝器。C11引入的可變參數(shù)模板就是為了徹底解決這個(gè)問(wèn)題。它允許模板接受任意數(shù)量、任意類型在類型安全的前提下的模板參數(shù)。這不僅僅是語(yǔ)法糖它從根本上改變了我們編寫泛型代碼的方式是C模板元編程和現(xiàn)代庫(kù)設(shè)計(jì)如標(biāo)準(zhǔn)庫(kù)中的tuple、bind、make_shared等的基石。簡(jiǎn)單說(shuō)它讓“不確定”變成了“確定”讓“有限”變成了“無(wú)限”是C邁向更高層次抽象的關(guān)鍵一步。2. 可變參數(shù)模板的核心語(yǔ)法拆解理解可變參數(shù)模板首先要掌握幾個(gè)核心概念和語(yǔ)法點(diǎn)。它們看起來(lái)有些奇特但一旦理解其模式就會(huì)覺(jué)得非常自然。2.1 基本語(yǔ)法模板參數(shù)包與函數(shù)參數(shù)包可變參數(shù)模板的核心是“參數(shù)包”。它分為兩種模板參數(shù)包在模板參數(shù)列表中用typename...或class...聲明表示可以接受零個(gè)或多個(gè)模板參數(shù)。函數(shù)參數(shù)包在函數(shù)參數(shù)列表中用Args...聲明表示可以接受零個(gè)或多個(gè)函數(shù)參數(shù)。// Args 是一個(gè)模板參數(shù)包表示零個(gè)或多個(gè)類型參數(shù) template typename... Args // args 是一個(gè)函數(shù)參數(shù)包其類型是 Args...表示零個(gè)或多個(gè)函數(shù)參數(shù) void print(Args... args) { // 函數(shù)體 }在這個(gè)聲明中Args是一個(gè)模板參數(shù)包它包含了傳遞給模板的所有類型。args是一個(gè)函數(shù)參數(shù)包它包含了傳遞給函數(shù)的所有值。Args和args的命名是約定俗成的你可以用任何合法的標(biāo)識(shí)符但Args表示參數(shù)類型和args表示參數(shù)值的組合非常清晰。注意typename...和class...在這里完全等價(jià)都用于聲明類型模板參數(shù)包。選擇哪一個(gè)取決于個(gè)人或團(tuán)隊(duì)的編碼風(fēng)格。2.2 參數(shù)包的展開(kāi)遞歸與折疊表達(dá)式聲明了參數(shù)包下一步就是使用它。由于參數(shù)包在編譯期是“一包”東西我們不能直接像操作單個(gè)參數(shù)那樣操作它比如直接用args[0]。我們必須通過(guò)“展開(kāi)”的方式來(lái)逐個(gè)處理包里的元素。C11主要提供了兩種展開(kāi)方式遞歸展開(kāi)和C17引入的折疊表達(dá)式我們先介紹遞歸因?yàn)樗亲罨A(chǔ)、最通用的方法。遞歸展開(kāi)的核心思想是提供一個(gè)處理“一個(gè)參數(shù)”的基準(zhǔn)情形函數(shù)和一個(gè)處理“一個(gè)參數(shù)剩余參數(shù)包”的遞歸情形函數(shù)模板。// 基準(zhǔn)情形處理零個(gè)參數(shù)遞歸的終點(diǎn) void print() { std::cout std::endl; } // 遞歸情形處理第一個(gè)參數(shù)t和剩余的參數(shù)包args... template typename T, typename... Args void print(T t, Args... args) { std::cout t ; // 處理當(dāng)前第一個(gè)參數(shù) print(args...); // 遞歸調(diào)用自身處理剩余的參數(shù)包 } // 使用 print(1, 2.5, hello, a); // 輸出1 2.5 hello a工作原理調(diào)用print(1, 2.5, hello, a)匹配遞歸版本。T被推導(dǎo)為intArgs...被推導(dǎo)為double, const char*, char。輸出1然后遞歸調(diào)用print(2.5, hello, a)。第二次匹配遞歸版本。T為doubleArgs...為const char*, char。輸出2.5遞歸調(diào)用print(hello, a)。第三次匹配遞歸版本。T為const char*Args...為char。輸出hello遞歸調(diào)用print(a)。第四次匹配遞歸版本。T為charArgs...為空包。輸出a遞歸調(diào)用print()。最終匹配無(wú)參數(shù)的基準(zhǔn)情形print()輸出換行遞歸終止。這個(gè)過(guò)程完全在編譯期展開(kāi)運(yùn)行時(shí)沒(méi)有遞歸的函數(shù)調(diào)用開(kāi)銷編譯器通常會(huì)進(jìn)行內(nèi)聯(lián)優(yōu)化。2.3 sizeof... 運(yùn)算符有時(shí)我們需要在編譯期知道參數(shù)包中有多少個(gè)參數(shù)這時(shí)就需要用到sizeof...運(yùn)算符。它返回一個(gè)編譯期常量。template typename... Args void countArgs(Args... args) { std::cout Number of type arguments: sizeof...(Args) std::endl; std::cout Number of function arguments: sizeof...(args) std::endl; } countArgs(1, 2.0, three); // 輸出Number of type arguments: 3 // Number of function arguments: 3sizeof...(Args)和sizeof...(args)通常相等它們分別計(jì)算類型包和函數(shù)參數(shù)包的大小。3. 可變參數(shù)模板的進(jìn)階應(yīng)用與實(shí)現(xiàn)掌握了基本語(yǔ)法后我們來(lái)看看可變參數(shù)模板在實(shí)際場(chǎng)景中如何大顯身手。這些應(yīng)用不僅是標(biāo)準(zhǔn)庫(kù)的基石也是我們?cè)O(shè)計(jì)靈活、強(qiáng)大接口的利器。3.1 完美轉(zhuǎn)發(fā)與通用包裝器這是可變參數(shù)模板最經(jīng)典、最強(qiáng)大的應(yīng)用之一。結(jié)合右值引用和**std::forward**我們可以創(chuàng)建一個(gè)“透明”的包裝函數(shù)將收到的所有參數(shù)原封不動(dòng)地、保持其值類別左值/右值地傳遞給另一個(gè)函數(shù)。這就是std::make_shared、std::make_unique以及很多工廠函數(shù)和裝飾器模式實(shí)現(xiàn)的核心。template typename T, typename... Args std::unique_ptrT make_unique(Args... args) { return std::unique_ptrT(new T(std::forwardArgs(args)...)); } class MyClass { public: MyClass(int a, double b, const std::string c) { std::cout Constructed with a , b , c std::endl; } }; int main() { // 完美轉(zhuǎn)發(fā)所有構(gòu)造參數(shù)給 MyClass 的構(gòu)造函數(shù) auto ptr make_uniqueMyClass(42, 3.14, hello); return 0; }關(guān)鍵點(diǎn)解析Args... args這里使用了萬(wàn)能引用。Args不是一個(gè)右值引用而是一個(gè)引用折疊規(guī)則下的通用引用它能根據(jù)傳入的實(shí)參自動(dòng)推導(dǎo)出是左值引用還是右值引用。std::forwardArgs(args)...這是參數(shù)包展開(kāi)的另一個(gè)例子。std::forward的作用是保持參數(shù)的值類別。這個(gè)表達(dá)式會(huì)為參數(shù)包中的每一個(gè)參數(shù)arg_i分別調(diào)用std::forwardArg_i_Type(arg_i)然后將結(jié)果列表傳遞給T的構(gòu)造函數(shù)。這種模式確保了無(wú)論調(diào)用者傳入的是左值、右值、const還是非const參數(shù)都能以正確的形式傳遞給目標(biāo)函數(shù)實(shí)現(xiàn)了“完美轉(zhuǎn)發(fā)”。3.2 實(shí)現(xiàn)簡(jiǎn)易的元組Tuple元組是可變參數(shù)模板的另一個(gè)絕佳展示。一個(gè)元組需要能存儲(chǔ)任意數(shù)量、任意類型的值。我們可以通過(guò)遞歸繼承的方式來(lái)實(shí)現(xiàn)。// 空元組作為遞歸基類 template typename... Types class Tuple; // 特化空參數(shù)包遞歸終點(diǎn) template class Tuple {}; // 遞歸定義包含一個(gè)頭元素 Head 和剩余元素的包 Tail... template typename Head, typename... Tail class TupleHead, Tail... : private TupleTail... { public: Tuple(const Head head, const Tail... tail) : TupleTail...(tail...), head_(head) {} // 獲取第N個(gè)元素需要一些模板元編程技巧使用遞歸和編譯期整數(shù)序列這里省略具體get實(shí)現(xiàn)。 // 標(biāo)準(zhǔn)庫(kù) std::tuple 的實(shí)現(xiàn)要復(fù)雜得多包括引用類型、移動(dòng)語(yǔ)義等。 Head getHead() { return head_; } const Head getHead() const { return head_; } TupleTail... getTail() { return *this; } const TupleTail... getTail() const { return *this; } private: Head head_; };實(shí)現(xiàn)思路Tupleint, double, std::string繼承自Tupledouble, std::string。Tupledouble, std::string繼承自Tuplestd::string。Tuplestd::string繼承自Tuple空基類。每個(gè)派生類存儲(chǔ)自己的“頭”元素并通過(guò)繼承獲得存儲(chǔ)剩余元素的基類。這樣就形成了一個(gè)遞歸的嵌套結(jié)構(gòu)將多個(gè)異質(zhì)元素打包在一個(gè)對(duì)象里。獲取第N個(gè)元素std::getN通常需要用到編譯期整數(shù)序列std::index_sequence和模板特化這是一個(gè)更深入的模板元編程話題。3.3 實(shí)現(xiàn)編譯期求和的示例我們可以利用可變參數(shù)模板和遞歸在編譯期計(jì)算參數(shù)包中所有參數(shù)的和。這展示了編譯期計(jì)算的能力。// 基準(zhǔn)情形空包的和為0 template typename... Args constexpr int sum() { return 0; } // C11 起 constexpr 可用 // 遞歸情形 template typename T, typename... Args constexpr auto sum(T t, Args... args) - decltype(t sum(args...)) { return t sum(args...); } // C17 折疊表達(dá)式版本更簡(jiǎn)潔 template typename... Args constexpr auto sum_fold(Args... args) { return (args ...); // 二元右折疊 } static_assert(sum(1, 2, 3, 4) 10, Compile-time sum failed); static_assert(sum_fold(1, 2, 3, 4) 10, Compile-time fold sum failed);遞歸版本分析sum(1, 2, 3, 4)展開(kāi)為1 sum(2,3,4)-1 (2 sum(3,4))-1 (2 (3 sum(4)))-1 (2 (3 (4 sum())))-1 (2 (3 (4 0)))。由于函數(shù)被聲明為constexpr這個(gè)計(jì)算過(guò)程可以在編譯期完成static_assert就是證明。返回類型使用了decltype和尾返回類型來(lái)推導(dǎo)確保類型安全。折疊表達(dá)式C17(args ...)是一個(gè)二元右折疊。它等價(jià)于(arg1 (arg2 (arg3 ...)))。折疊表達(dá)式語(yǔ)法大大簡(jiǎn)化了需要對(duì)參數(shù)包進(jìn)行二元操作的代碼是處理參數(shù)包的現(xiàn)代首選方式。4. 可變參數(shù)模板的實(shí)戰(zhàn)技巧與避坑指南在實(shí)際使用中有一些細(xì)節(jié)和陷阱需要特別注意。這些經(jīng)驗(yàn)往往來(lái)自踩過(guò)的坑。4.1 處理零參數(shù)的情況這是新手最容易忽略的一點(diǎn)。如果你的可變參數(shù)函數(shù)模板可能接受零個(gè)參數(shù)必須提供處理零個(gè)參數(shù)的特化或重載版本否則會(huì)導(dǎo)致編譯錯(cuò)誤找不到匹配的函數(shù)。// 錯(cuò)誤示例缺少零參數(shù)版本 template typename... Args void bad_print(Args... args) { // 假設(shè)這里需要展開(kāi) args... // 當(dāng) args... 為空時(shí)下面的展開(kāi)可能無(wú)意義或錯(cuò)誤 (std::cout ... args) std::endl; // C17折疊表達(dá)式空包時(shí)可能有問(wèn)題取決于運(yùn)算符 } // bad_print(); // 可能編譯錯(cuò)誤或行為未定義 // 正確做法提供零參數(shù)版本 void good_print() { std::cout std::endl; } template typename T, typename... Args void good_print(T t, Args... args) { std::cout t ; good_print(args...); } // good_print(); // 正確調(diào)用輸出一個(gè)空行對(duì)于C17的折疊表達(dá)式空參數(shù)包的行為是定義良好的但結(jié)果取決于使用的運(yùn)算符。對(duì)于(args ...)這樣的二元折疊空包通常是非法的除非指定了初始值如(0 ... args)。對(duì)于輸出流運(yùn)算符空包可能沒(méi)問(wèn)題但為了代碼清晰和兼容性顯式處理零參數(shù)情況是更好的實(shí)踐。4.2 參數(shù)包的轉(zhuǎn)發(fā)與引用折疊在編寫通用轉(zhuǎn)發(fā)函數(shù)時(shí)必須正確使用std::forward來(lái)保持參數(shù)的左右值屬性。錯(cuò)誤使用會(huì)導(dǎo)致不必要的拷貝或編譯錯(cuò)誤。template typename... Args void forward_wrong(Args... args) { // 按值傳遞丟失了右值信息 target_function(args...); // 所有參數(shù)都以左值形式傳遞 } template typename... Args void forward_correct(Args... args) { // 萬(wàn)能引用 target_function(std::forwardArgs(args)...); // 完美轉(zhuǎn)發(fā) }關(guān)鍵區(qū)別forward_wrong中即使傳入一個(gè)右值如42或std::move(x)args也會(huì)是int和X類型的值在傳遞給target_function時(shí)會(huì)發(fā)生拷貝。forward_correct中Args會(huì)推導(dǎo)出正確的引用類型。std::forwardArgs(args)...會(huì)為每個(gè)參數(shù)恢復(fù)其原始的值類別如果是右值就轉(zhuǎn)發(fā)為右值觸發(fā)移動(dòng)構(gòu)造或移動(dòng)賦值提升效率。4.3 遞歸深度限制與編譯器差異遞歸展開(kāi)可變參數(shù)模板會(huì)在編譯期生成一系列的模板實(shí)例。雖然現(xiàn)代編譯器優(yōu)化得很好但過(guò)深的遞歸比如參數(shù)包有幾千個(gè)參數(shù)可能會(huì)觸及編譯器的遞歸實(shí)例化深度限制導(dǎo)致編譯錯(cuò)誤。// 生成一個(gè)包含大量參數(shù)的調(diào)用 templateint... Is void huge_pack() { print(Is...); // 如果Is... 包含10000個(gè)整數(shù) }大多數(shù)編譯器如GCC, Clang都提供了選項(xiàng)來(lái)調(diào)整這個(gè)限制例如GCC的-ftemplate-depth。但在實(shí)際項(xiàng)目中遇到這種情況的概率極低。如果真有此需求可能需要重新設(shè)計(jì)或者使用C17的折疊表達(dá)式來(lái)避免遞歸。另外不同編譯器對(duì)可變參數(shù)模板的支持細(xì)節(jié)和錯(cuò)誤信息可能不同。在編寫跨平臺(tái)代碼時(shí)建議在主要編譯器MSVC GCC Clang上進(jìn)行測(cè)試。4.4 與C風(fēng)格可變參數(shù)函數(shù)的區(qū)別務(wù)必分清C可變參數(shù)模板和C語(yǔ)言風(fēng)格的va_list/va_start/va_arg。它們是截然不同的東西特性C 可變參數(shù)模板C風(fēng)格可變參數(shù) (...)類型安全是。每個(gè)參數(shù)的類型在編譯期都是已知且檢查的。否。類型信息丟失依賴程序員通過(guò)格式字符串等方式保證極易出錯(cuò)。性能編譯期展開(kāi)通常零開(kāi)銷。運(yùn)行時(shí)通過(guò)棧幀遍歷參數(shù)有開(kāi)銷??商幚眍愋腿魏晤愋桶悓?duì)象、引用等。僅限于“平凡”類型對(duì)于非POD類型行為未定義。使用場(chǎng)景泛型庫(kù)、元編程、完美轉(zhuǎn)發(fā)。與C接口交互、printf類函數(shù)。絕對(duì)不要在C新代碼中使用C風(fēng)格可變參數(shù)來(lái)處理泛型需求。printf家族函數(shù)是歷史遺留新的代碼庫(kù)應(yīng)使用類型安全的替代方案如iostream或fmtlib。5. C17折疊表達(dá)式更優(yōu)雅的展開(kāi)方式C17引入的折疊表達(dá)式極大地簡(jiǎn)化了對(duì)參數(shù)包進(jìn)行二元運(yùn)算的代碼讓我們可以告別繁瑣的遞歸展開(kāi)寫出更簡(jiǎn)潔、更直觀的代碼。5.1 四種折疊形式折疊表達(dá)式有四種基本形式區(qū)別在于括號(hào)位置和折疊方向一元右折疊(pack op ...)展開(kāi)為(pack1 op (pack2 op (pack3 op ...)))一元左折疊(... op pack)展開(kāi)為(((pack1 op pack2) op pack3) op ...)二元右折疊(pack op ... op init)展開(kāi)為(pack1 op (pack2 op (pack3 op ... op init)))二元左折疊(init op ... op pack)展開(kāi)為((((init op pack1) op pack2) op pack3) op ...)其中op是任何二元運(yùn)算符如,-,*,/,,,,,||等pack是參數(shù)包init是一個(gè)初始值。5.2 實(shí)用示例// 1. 求和我們已經(jīng)見(jiàn)過(guò) templatetypename... Args auto sum(Args... args) { return (args ...); // 一元右折疊等價(jià)于 args1 (args2 (args3 ...)) } // 2. 打印所有參數(shù)比遞歸版本簡(jiǎn)潔太多 templatetypename... Args void fold_print(Args... args) { (std::cout ... args) std::endl; // 一元左折疊注意 是左結(jié)合 // 展開(kāi)為(((std::cout arg1) arg2) arg3) ... } fold_print(1, is , 3.14); // 輸出1 is 3.14 // 3. 檢查所有參數(shù)是否為真邏輯與 templatetypename... Args bool all_true(Args... args) { return (args ...); // 一元右折疊 } bool b all_true(true, true, false, true); // b false // 4. 用逗號(hào)連接參數(shù)利用逗號(hào)運(yùn)算符 templatetypename... Args void call_with_comma(Args... args) { (some_function(args), ...); // 一元右折疊展開(kāi)為 (some_function(arg1), (some_function(arg2), ...)) // 會(huì)依次對(duì)每個(gè)參數(shù)調(diào)用 some_function } // 5. 帶初始值的折疊處理空包安全 templatetypename... Args auto safe_sum(Args... args) { return (0 ... args); // 二元左折疊即使args為空包結(jié)果也是0 } static_assert(safe_sum() 0); // 空包調(diào)用安全折疊表達(dá)式讓代碼意圖一目了然徹底解放了我們對(duì)參數(shù)包進(jìn)行常見(jiàn)聚合操作的生產(chǎn)力。6. 可變參數(shù)模板在元編程中的高級(jí)應(yīng)用可變參數(shù)模板與sizeof...、類型萃取、編譯期條件判斷等結(jié)合可以實(shí)現(xiàn)強(qiáng)大的編譯期邏輯。6.1 編譯期判斷參數(shù)包是否包含特定類型#include type_traits // 輔助模板判斷類型T是否在類型列表Types...中 template typename T, typename... Types struct contains_type; // 遞歸基空列表不包含 template typename T struct contains_typeT : std::false_type {}; // 遞歸步如果第一個(gè)類型匹配則為true否則檢查剩余列表 template typename T, typename U, typename... Rest struct contains_typeT, U, Rest... : std::conditional_tstd::is_same_vT, U, std::true_type, contains_typeT, Rest... {}; // 變量模板便利版 template typename T, typename... Types inline constexpr bool contains_type_v contains_typeT, Types...::value; static_assert(contains_type_vint, double, char, int true); static_assert(contains_type_vfloat, double, char, int false);這個(gè)例子展示了如何使用模板特化和遞歸在編譯期遍歷類型包。std::conditional_t是編譯期的if-elsestd::true_type和std::false_type是代表布爾值的類型。6.2 實(shí)現(xiàn)一個(gè)編譯期獲取參數(shù)包第N個(gè)類型的工具template size_t N, typename... Types struct nth_type; // 特化獲取第0個(gè)類型Head template typename Head, typename... Tail struct nth_type0, Head, Tail... { using type Head; }; // 遞歸獲取第N個(gè)類型等同于獲取剩余列表的第N-1個(gè)類型 template size_t N, typename Head, typename... Tail struct nth_typeN, Head, Tail... { static_assert(N sizeof...(Tail) 1, Index out of bounds); using type typename nth_typeN - 1, Tail...::type; }; template size_t N, typename... Types using nth_type_t typename nth_typeN, Types...::type; static_assert(std::is_same_vnth_type_t1, int, double, char, double);這個(gè)nth_type元函數(shù)是std::tuple_element的原理簡(jiǎn)化版。它通過(guò)遞歸和模板特化在編譯期“計(jì)算”出類型列表中的第N個(gè)類型。static_assert用于提供編譯期邊界檢查。7. 總結(jié)與個(gè)人心得可變參數(shù)模板從C11的“新特性”如今已成為現(xiàn)代C高級(jí)編程的標(biāo)配。它和右值引用、移動(dòng)語(yǔ)義、auto、lambda表達(dá)式等特性一起構(gòu)成了現(xiàn)代C區(qū)別于舊時(shí)代C的骨架?;仡櫿麄€(gè)學(xué)習(xí)過(guò)程我有幾點(diǎn)深刻的體會(huì)第一理解遞歸模式是關(guān)鍵。無(wú)論是函數(shù)遞歸還是通過(guò)繼承/組合的遞歸都是處理參數(shù)包最基礎(chǔ)、最本質(zhì)的方法。即使有了折疊表達(dá)式理解遞歸展開(kāi)的思維模型對(duì)于閱讀舊代碼和理解模板元編程的底層邏輯仍然至關(guān)重要。第二完美轉(zhuǎn)發(fā)是“殺手級(jí)”應(yīng)用。工廠函數(shù)、包裝器、線程池任務(wù)提交……幾乎所有需要泛化參數(shù)傳遞的地方都離不開(kāi)template typename... Args void f(Args... args)和std::forwardArgs(args)...這個(gè)黃金組合。它完美解決了參數(shù)傳遞中的效率與正確性問(wèn)題。第三編譯期計(jì)算潛力巨大。結(jié)合constexpr、if constexprC17和可變參數(shù)模板我們可以在編譯期完成很多原本需要在運(yùn)行期做的計(jì)算和類型操作比如上面提到的類型判斷、索引查找甚至是更復(fù)雜的元程序。這不僅能提升運(yùn)行時(shí)性能移除了計(jì)算開(kāi)銷還能通過(guò)編譯期檢查增強(qiáng)類型安全。第四從“能用”到“用好”有距離。剛開(kāi)始接觸時(shí)很容易寫出編譯通過(guò)但存在潛在問(wèn)題的代碼比如忘記處理空包、錯(cuò)誤地傳遞了引用類別。多寫、多試、多讀標(biāo)準(zhǔn)庫(kù)的實(shí)現(xiàn)如tuple,utility的源碼或文檔是跨越這個(gè)距離的唯一途徑。特別是調(diào)試模板錯(cuò)誤時(shí)編譯器給出的錯(cuò)誤信息可能非常冗長(zhǎng)晦澀耐心地從第一條錯(cuò)誤信息看起往往能找到問(wèn)題的根源。最后雖然折疊表達(dá)式讓代碼簡(jiǎn)潔了許多但在一些復(fù)雜的、非線性的參數(shù)處理場(chǎng)景比如需要根據(jù)參數(shù)類型執(zhí)行不同分支邏輯遞歸展開(kāi)仍然是更靈活的選擇。工具沒(méi)有絕對(duì)的好壞只有是否適合當(dāng)下的場(chǎng)景。掌握可變參數(shù)模板就是為你的C工具箱里添加了一件應(yīng)對(duì)“不確定性”的利器。