2011-06-17 61 views
0

我一直在試驗可變參數模板和參數轉發。我認爲我發現了一些不一致的行爲。這個可變參數模板參數推導是否正確?

爲了說明,此程序:

#include <iostream> 
#include <typeinfo> 
#include <tuple> 
#include <cxxabi.h> 

template <typename... Args> struct X {}; 

struct A { 
    A() {} 
    A (const A &) { 
     std :: cout << "copy\n"; 
    } 
}; 

template <typename T> const char * type_name() { 
    return abi :: __cxa_demangle (typeid (T) .name(), 0, 0, NULL); 
} 

void foo() {} 

template <typename... Args> 
void foo (const A &, Args ... args) { 
    std :: cout << type_name <X <Args...>>() << "\n「; foo (args...); 
} 

int main() { 
    foo (A(), A()); 
} 

輸出如下:

X<A, A> 
copy 
X<A> 

即使foo的模板特具有const引用第一個參數,可變參數是按值傳遞,因爲模板類型被推斷爲非參考類型,如X<A>輸出所示。

所以,我諮詢Thomas Becker

template<typename T> 
void foo(T&&); 

這裏,按照以下規定:

  1. 當調用函數foo在A型的左值,則T解析爲&和因此,通過上述參考摺疊 規則,參數類型 實際上變成A &。

  2. 當FOO上稱爲類型A的右值,則T解析爲A,和 因此參數類型成爲A & &。

而且試試這個:

template <typename... Args> 
void foo (const A &, Args && ... args) { 
    std :: cout << type_name<X<Args...>>() << "\n"; 
    foo (args...); 
} 

,輸出:

X<A, A> 
X<A&> 

現在我很爲難。這裏有三個foo調用。在我的腦海裏,main()應該推導出foo<A,A,A>(A&&,A&&,A&&)(因爲A()是一個未命名的右值,因此是一個參考),它超載 - 解析爲foo<A,A,A>(const A&,A&&,A&&)。這又推導出foo<A,A>(A&&,A&&)等等。

問題是:爲什麼X<A,A>有非參考A s但X<A&>有一個參考A

這會導致問題,因爲我無法在遞歸中使用std::forward

回答

2

首先,我假設你貼錯了主,正確的是:

int main() { 
     foo (A(), A(), A()); 
} 

我希望我猜對了,否則這個帖子的其餘部分是無效的:)
其次,我不標準術語非常好,所以我希望以下內容不太不精確。

我經常覺得理解variadic模板會發生什麼的最簡單方法就是生成一個編譯器會有效執行的操作。

例如你的第一次嘗試

void foo (const A &, Args ... args)

實際上是由編譯器解壓到像這3個功能:

void foo3 (const A & a, A a1, A a2)
void foo2 (const A & a, A a0)
void foo1 (const A & a)

當調用foo3(A(), A(), A());在mai中編譯器會進行優化,只是默認構造a1和a2而不是default-construct和copy。但是,當在foo3中調用foo2時,編譯器無法再優化,因此在調用foo2(a1, a2時)foo2中的參數a0必須是複製構造的。這是我們可以在跟蹤中看到的a0的複製構造(「複製」)

順便說一下,我不明白爲什麼您通過const ref傳遞第一個元素,而其餘的值是其他值。爲什麼不通過const-ref傳遞所有內容?

void foo (const A &, const Args& ... args)

OK,現在你的第二次嘗試與右值-REF:

void foo (const A &, Args && ... args)

當調用主foo3(A(), A(), A());A()是一個右值所以這個規則適用:

當在類型A的右值 的右值上調用foo時,則T解析爲A,並且 因此參數類型變爲A & &。

所以foo3看起來是這樣的:

void foo3 (const A &, A && a1, A && a2) { 
    std :: cout << type_name<X<A, A>>() << "\n"; 
    foo2 (a1, a2); 
} 

但是這裏要小心。 a1和a2不再是右值。他們有一個名字,因此他們是左撇子。

所以現在內部foo2的,此規則也適用:

當FOO上調用 類型A的左值,則T解析爲&和 因此,由參考塌陷如上所述, 規則參數類型 實際上變成A &。

內foo2的那麼A0類型將有效地爲A &,這就是爲什麼我們能在痕跡看X<A&>

要使用完美轉發我想你需要放棄的這個奇怪的假設:

這會導致一個問題,因爲我不能 使用std ::向前遞歸。

我不明白爲什麼。下面的代碼應該是正確的:

void foo(A&&, Args&&... args) 
{ 
    std::cout << type-name<X<Args...>>() << "\n"; 
    foo(std::forward<Args>(args...)); 
} 

當調用foo()爲遞歸的std ::前進的意志變成每個ARGS ...從左值再次右值,因此扣除將是正確的。