2012-12-31 65 views
5

以下程序的輸出...模板構造函數優先於普通副本並移動構造函數?

#include <iostream> 

using namespace std; 

struct X 
{ 
    X(const X&)    { cout << "copy" << endl; } 
    X(X&&)     { cout << "move" << endl; } 

    template<class T> X(T&&) { cout << "tmpl" << endl; } 
}; 

int main() 
{ 
    X x1 = 42; 
    X x2(x1); 
} 

tmpl 
tmpl 

所需的輸出是:

tmpl 
copy 

爲什麼沒有具體的拷貝構造函數優先於模板構造函數?

有沒有辦法修復它,以便複製和移動構造函數重載將優先於模板構造函數?

+0

這裏是與這個問題有關的最終課程:http://codereview.stackexchange.com/questions/20058/a-c11-any-class –

回答

2

正常的重載解析規則在選擇構造函數時仍然適用 - 而構造函數採用非常量左值引用(對於參數推導後的模板構造函數)比採用常量左值引用的構造函數更適合。

當然,您可以添加另一個重載,取非const常量左值引用,即

X(X&)    { cout << "copy" << endl; } 

更新:其他情形的模板的構造是一個更好的匹配:

const X f() 
{ return X(); } 

struct Y : X 
{ Y() { } }; 

int main() 
{ 
    X x3(f()); // const-qualified rvalue 
    Y y; 
    X x4(y); // derived class 
} 
+0

我是否還需要添加'X(const X &&)'或是這不可能?有沒有其他的情況下,移動或複製構造函數在模板構造函數的空白處匹配? –

+0

@AndrewTomazosFathomlingCorps:SFINAE可以幫助你的出現,讓你可以同時擁有你的函數簽名*完整*(我認爲這是很重要的)。例如,您可以在函數模板中使用'std :: enable_if'來僅啓用所需的情況。 – Nawaz

+0

是的,如果你碰巧有一個常數rvalue,例如從'const X f();'和'X x3(f())'。然後就是初始化器是一個派生類,它也會使用模板構造函數。 – cmeerw

5

那麼,這是因爲reference-collapsing

在重載解析階段,當功能模板實例,T被deduded爲X&,所以T&&(這是X& &&)變爲X&由於參考塌陷,並從功能模板的實例化的功能變得確切匹配和複製構造函數需要X&const X&的轉換(這就是爲什麼它不被選擇爲劣等匹配)。

但是,如果您從複製構造函數中刪除const,則將首選複製構造函數。試試這個:

X(/*const*/ X&) { cout << "copy" << endl; } 

Output如預期的那樣。

或者,如果你把參數的函數模板爲const T&然後拷貝構造函數會被調用(即使它仍然是一樣的!),因爲參考塌陷不會進入現在圖片:

template<class T> X(const T &) { cout << "tmpl" << endl; } 

Output預計,再次。

3

如果你不希望添加另一個構造函數(如其他答案建議),則可以使用SFINAE以限制呼叫,通過這種更換模板的構造函數:

template<class T 
    , typename std::enable_if<not std::is_same<X, typename std::decay<T>::type>::value, int>::type = 0 
> X(T&&) { cout << "tmpl " << endl; } 

剛剛涉及添加一個dummy默認TEMPL吃了一個參數(一種已知技術:http://www.boost.org/doc/libs/1_52_0/libs/utility/enable_if.html)。沒有額外的標題是必要的。

您將獲得所需的輸出。

我從一個相關的問題得到了這個答案:http://flamingdangerzone.com/cxx11/2012/06/05/is_related.html。所有這些看起來相當不雅,但似乎是目前唯一的出路。我仍然希望看到更優雅的解決方案。