2014-09-05 198 views
56

我正在嘗試學習可變參數模板和函數。我不明白爲什麼這個代碼不編譯:可變模板包擴展

template<typename T> 
static void bar(T t) {} 

template<typename... Args> 
static void foo2(Args... args) 
{ 
    (bar(args)...); 
} 

int main() 
{ 
    foo2(1, 2, 3, "3"); 
    return 0;  
} 

當我編譯失敗,錯誤:

Error C3520: 'args': parameter pack must be expanded in this context

(在功能foo2)。

+1

如果'Args'爲空? – CoffeeandCode 2014-09-05 07:28:15

回答

86

包擴展可能發生的地方之一是括號初始化列表。您可以通過將擴展虛擬陣列的初始化列表裏利用這一點:

template<typename... Args> 
static void foo2(Args &&... args) 
{ 
    int dummy[] = { 0, ((void) bar(std::forward<Args>(args)), 0) ... }; 
} 

爲了解釋初始化的更多的詳細內容:

{ 0, ((void) bar(std::forward<Args>(args)), 0) ... }; 
    |  |  |      |  | 
    |  |  |      |  --- pack expand the whole thing 
    |  |  |      | 
    |  |  --perfect forwarding  --- comma operator 
    |  | 
    |  -- cast to void to ensure that regardless of bar()'s return type 
    |   the built-in comma operator is used rather than an overloaded one 
    | 
    ---ensure that the array has at least one element so that we don't try to make an 
    illegal 0-length array when args is empty 

Demo

擴大{}的一個重要優勢是它保證了從左到右的評估。


用C++ 1Z fold expressions,你可以只寫

((void) bar(std::forward<Args>(args)), ...); 
+13

我看到的一個變體是'using expander = int []; expander {...};'因爲那麼數組變量沒有名字,編譯器顯然明白這個數組並不需要我創建或使用。 – 2014-09-05 16:20:15

+2

第二個'0'是什麼?緊接逗號運算符 – 2015-07-27 21:20:35

+2

@AaronMcDaid後面的那個整數表達式的類型是int,並且與數組的元素類型匹配。 – 2015-07-27 21:22:02

36

參數包只能在嚴格定義的上下文列表中進行擴展,並且運算符,不是其中之一。換句話說,不可能使用包擴展來生成由運算符,分隔的一系列子表達式組成的表達式。

經驗法則是「擴張能夠產生列表 - 分隔,模式其中,列表分隔符。」運算符,不構造語法意義上的列表。

要調用函數的每個參數,你可以使用遞歸(這是在可變參數模板編程的盒子的主要工具):

template <typename T> 
void bar(T t) {} 

void foo2() {} 

template <typename Car, typename... Cdr> 
void foo2(Car car, Cdr... cdr) 
{ 
    bar(car); 
    foo2(cdr...); 
} 

int main() 
{ 
    foo2 (1, 2, 3, "3"); 
} 

Live example

+2

該死的,你只是打賭我回答這個*搖晃拳頭*,但你應該增加完美的轉發給你的答案;這也是「可變模板程序員框中的主要工具」。 – CoffeeandCode 2014-09-05 07:26:01

+0

謝謝你的回答。我知道遞歸實現。我只想找到解決方法來編譯沒有遞歸和新函數的代碼。 – 2014-09-05 07:26:04

+1

@ViacheslavDronov看起來好像你正在使用模板:你已經有一個由編譯器生成的函數的垃圾負載,爲什麼不把它添加到該列表? – CoffeeandCode 2014-09-05 07:27:43

13

SHAMELESS COPY [approved by its source]

參數組可以只能在嚴格定義的上下文列表中擴展,並且運算符,不是其中之一。換句話說,不可能使用包擴展來生成由運算符,分隔的一系列子表達式組成的表達式。

經驗法則是「擴展可以生成,-分離的模式列表,其中,是列表分隔符。」運算符,不構造語法意義上的列表。

要呼籲每一個參數的函數,你可以使用遞歸(這是在可變參數模板編程的盒子的主要工具):

#include <utility> 

template<typename T> 
void foo(T &&t){} 

template<typename Arg0, typename Arg1, typename ... Args> 
void foo(Arg0 &&arg0, Arg1 &&arg1, Args &&... args){ 
    foo(std::forward<Arg0>(arg0)); 
    foo(std::forward<Arg1>(arg1), std::forward<Args>(args)...); 
} 

auto main() -> int{ 
    foo(1, 2, 3, "3"); 
} 

有用的非複製的信息

你可能還沒有在這個答案中看到的另一件事是使用&&說明符和std::forward。在C++中,&&說明符可以表示以下兩種情況之一:右值引用或通用引用。

我不會進入右值引用,但有人使用可變參數模板;普遍的參考文獻是上帝派來的。

完美轉發

一個std::forward和普遍引用的用途是類型的其他功能完美轉發。

在您的例子,如果我們通過一個int&foo2它會自動降級爲int,因爲模板扣除後所產生的foo2功能的簽名,如果你想要再往前這個arg到將修改它的另一個功能ny引用,你會得到不想要的結果(該變量不會被改變),因爲foo2將通過傳遞一個int給它傳遞一個臨時引用。爲了解決這個問題,我們指定一個轉發功能,將任意類型的參考轉換爲變量(右值爲左值)。然後,爲了確保我們傳遞的轉發函數中傳遞的確切類型,我們使用std::forward,然後只有那麼我們是否允許降級類型;因爲我們現在處於最重要的地步。

如果需要,請閱讀universal referencesperfect forwarding;斯科特邁爾斯作爲一種資源相當出色。

1

可以使用make_tuple的包擴展,因爲它引入了一個情境,其中通過膨脹產生的,序列有效

make_tuple((bar(std::forward<Args>(args)), 0)...); 

現在,我懷疑產生的零的未使用/未命名/臨時元組可以被編譯器分離並優化掉

Demo

-1

執行順序無法保證!

make_tuple((bar(std::forward<Args>(args)), 0)...); 

在這個例子中,參數將按照與GCC至少相反的順序打印。

foo2(1, 2, 3, "3"); 

calling bar for 3 
calling bar for 3 
calling bar for 2 
calling bar for 1