2010-05-01 52 views
13

(我問在comp.std.C++這個問題的變化,但沒有得到答案。)的C++ 0x右值引用和臨時

電話爲什麼要f(arg)在此代碼調用const ref超載f

void f(const std::string &); //less efficient 
void f(std::string &&); //more efficient 

void g(const char * arg) 
{ 
    f(arg); 
} 

我的直覺說,f(string &&)過載應選擇,因爲arg需要被轉換成一個臨時的不管是什麼,以及臨時的右值引用比左值基準更好的匹配。

這不是在 GCC和 MSVC(編輯:感謝Sumant:在GCC 4.3-4.5中沒有發生)中發生的情況。至少在 G ++和 MSVC中,任何左值都不會綁定到右值引用參數,即使有也存在臨時創建的中間值。的確,如果const ref overload不存在,編譯器會診斷錯誤。但是,編寫f(arg + 0)f(std::string(arg))確實如您所期望的那樣選擇右值引用超載。

從我讀的C++ 0x標準看來,當考慮f(string &&)是否可行時,應該考慮將const char *隱式轉換爲字符串,就像傳遞const lvalue ref參數時一樣。第13.3節(重載決議)在太多地方不區分右值參考和常量參考。此外,似乎阻止左值綁定到右值引用(13.3.3.1.4/3)的規則不應該適用於臨時存在中間值的情況 - 畢竟,臨時移動是完全安全的。

是這樣的:

  1. 我誤讀/誤解的標準,在實施的行爲是預期的行爲,並有一些很好的理由,爲什麼我的例子應該表現它的方式?
  2. 編譯器供應商以某種方式產生的錯誤?還是基於共同實施策略的錯誤?或者在例如GCC(這個左值/右值引用綁定規則是第一次實現的),這是由其他供應商複製的?
  3. 標準中的缺陷,或意想不到的後果,還是應該澄清的事情?

編輯:我有一個後續的問題是相關的:C++0x rvalue references - lvalues-rvalue binding

+0

在給定的情況下,爲什麼會超載更快?一個字符串對象必須以某種方式創建,並綁定一個引用。 – UncleBens 2010-05-01 08:11:16

+0

@UncleBens:假設函數將其參數存儲在某個全局變量/成員變量中 - 如果已知該參數是可移動的,那麼它可以移動到目標中,而不是被複制。 – Doug 2010-05-01 08:21:01

回答

8

GCC根據FCD做錯了。的FCD說在8.5.3約參照結合

  • 如果參考是一個左值參考和初始化表達式是一個[左值/類型] ...
  • 否則,應參考是一個左值參照非易失性常量類型(即cv1應爲const),或者引用應爲右值引用,並且初始化表達式應爲右值或具有函數類型。

您呼叫的std::string &&匹配的情況下沒有人,因爲初始化是左值。它沒有到達創建臨時右值的地方,因爲頂層子彈已經需要右值。

現在,重載分辨率不直接使用引用綁定來查看是否存在隱式轉換序列。取而代之的是,它說在13.3.3.1.4/2

當引用類型的參數不直接結合到一個參數表達式,轉換序列是根據13.3到參數表達式轉換爲基礎類型的參考的所需要的一個。 3.1。

因此,重載決議算出勝者,儘管勝者實際上可能無法約束該論點。例如:

struct B { B(int) { /* ... */ } }; 
struct A { int bits: 1; }; 

void f(int&); 
void f(B); 
int main() { A a; f(a.bits); } 

參考在8.5結合禁止位域綁定到左值的引用。但重載分辨率表示轉換序列是轉換爲int的轉換序列,因此即使稍後進行調用,該調用也是成功的,因此調用不合格。因此,我的位域示例不合格。如果選擇B版本,它將會成功,但需要用戶定義的轉換。

但是,該規則存在兩個例外。這些

除隱式對象參數,爲此,請參見13.3.1,不能如果它需要一個左值參照非const綁定到右值或結合一個rvalue參照一個形成一個標準轉換序列左值。

因此,下面的調用是有效的:

struct B { B(int) { /* ... */ } }; 
struct A { int bits: 1; }; 

void f(int&); /* binding an lvalue ref to non-const to rvalue! */ 
void f(B); 
int main() { A a; f(1); } 

就這樣,你的榜樣調用const T&版本

void f(const std::string &); 
void f(std::string &&); // would bind to lvalue! 

void g(const char * arg) { f(arg); } 

但是,如果你說f(arg + 0),爲您創造一個右值,並因此第二個功能是可行的。

+0

啊,我沒有仔細閱讀8.5.3,謝謝。看起來好像綁定引用的規則是不必要的嚴格 - 我沒有看到有任何令人信服的用例拒絕這樣的代碼,但它可能(意外地,IMO)導致意外的副本 - 例如,如果你用const char * lvalue參數調用矢量 :: push_back。 – Doug 2010-05-01 11:03:38

+1

還有一件事 - 禁止非常量左值參考接受臨時對象(對我而言)特別是,您不會意外丟失函數可能通過該參考返回的任何「額外」信息。但是這個考慮並不適用於右值,無論是常量還是非常量 - 在調用之後它在概念上「不是有用的值」。最後,允許f(arg)但允許f(arg + 0)或者例如f stringvec.push_back(string(charptr))比stringvec.push_back(charptr)更高效*對我來說非常不直觀。 – Doug 2010-05-01 12:52:45

+0

這個答案不是最新的,是嗎?目前的草案要求第二次重載沒有任何錯誤。 – sellibitze 2011-08-27 17:18:13

1

很多標準的當前草案的事情需要澄清,如果你問我。編譯器仍在開發中,所以很難相信他們的幫助。

它看起來很清楚你的直覺是正確的......任何類型的臨時都應該綁定到右值引用。例如,§3.10,新的「分類法」部分,將臨時對象明確定義爲rvalues。

問題可能是RR參數規範不足以調用臨時創建。 §5.2.2/ 5:「如果參數是const引用類型,則在需要時引入臨時對象。」這聽起來很可疑。

似乎在§13.3.3再次滑過裂縫。1/6:(重點煤礦)

當參數類型不是參考,隱式轉換序列模型從參數表達的參數的副本初始化。隱式轉換序列是將參數表達式轉換爲參數類型的前值所需的轉換序列。

請注意,複製初始化string &&rr = "hello";在GCC中正常工作。

編輯:其實問題不存在於我的GCC版本。我仍然試圖弄清楚用戶定義的轉換序列的第二個標準轉換如何與形成右值引用相關。 (RR是形成於所有的轉化,或者它被分散花絮像5.2.2/5決定?)

+0

謝謝 - 我錯過了你引用的那些條款。他們似乎混淆了事情。 此外,我沒有傳遞字符串文字的原因是因爲MSVC似乎把它們當作左值,但是GCC不會 - 這很奇怪。 – Doug 2010-05-01 07:09:27

2

我沒有看到Doug在g ++上提到的行爲。 g ++ 4.5和4.4.3都如預期般呼叫f(string &&),但VS2010呼叫f(const string &)。你使用哪個g ++版本?

+0

+1,我希望在跳入之前驗證過問題! – Potatoswatter 2010-05-01 05:04:41

+0

我重新檢查了G ++,你是對的 - 它不會發生。我確定我之前在那裏檢查過!看來也許問題只存在於MSVC中,或者標準中還不清楚。 – Doug 2010-05-01 07:03:16

0

我不知道這個標準的最新版本是否發生了變化,但它曾經說過「如果有疑問,請不要使用右值引用」。可能出於兼容性原因。

如果您想要移動語義,請使用f(std::move(arg)),它適用於兩種編譯器。

6

這是您閱讀的標準草案中的一個缺陷。出於安全原因,這種缺陷被認爲是一些急切的編輯的副作用,以禁止右值引用與左值的綁定。

你的直覺是對的。當然,即使初始值設定項是一個左值表達式,允許右值引用引用某個未命名的臨時值也沒有什麼壞處。畢竟,這是右值引用的用途。你觀察到的問題去年得到了解決。即將到來的標準將要求在你的例子中選擇第二個重載,其中右值引用將引用一些臨時字符串對象。

規則修復使它進入n3225.pdf草案(2010-11-27):

  • [...]
  • 否則,應參考是一個左值參照非易失性常量類型(即cv1應爲const),或者引用應爲右值引用 ,初始值表達式應爲右值或具有函數類型 。 [...]
    • [...]
    • 否則,暫時的[...],創建[...]
 double&& rrd3 = i; // rrd3 refers to temporary with value 2.0 

但N3225似乎錯過了這個例子中的i。最新的N3290草案中包含這些例子:

 double d2 = 1.0; 
     double&& rrd2 = d2; // error: copying lvalue of related type 
     int i3 = 2; 
     double&& rrd3 = i3; // rrd3 refers to temporary with value 2.0 

由於您的MSVC版本發佈這一問題得到了固定前,它仍然按照老規則處理右值引用。預計下一個MSVC版本將實現新的右值引用規則(由MSVC開發人員稱爲「右值引用2.1」)see link