![](/img/trans.png)
[英]Why explicit-ness of std::pair's heterogeneous "move"-constructor changed in C++17?
[英]C++17: explicit conversion function vs explicit constructor + implicit conversions - have the rules changed?
Clang 6,clang 7和gcc 7.1,7.2和7.3都同意以下是有效的C ++ 17代碼,但在C ++ 14和C ++ 11下是不明確的。 MSVC 2015和2017也接受它。 但是,即使在c ++ 17模式下,gcc-8.1和8.2也拒絕它:
struct Foo
{
explicit Foo(int ptr);
};
template<class T>
struct Bar
{
operator T() const;
template<typename T2>
explicit operator T2() const;
};
Foo foo(Bar<char> x)
{
return (Foo)x;
}
接受它的編譯器選擇模板顯式轉換函數Bar::operator T2()
。
拒絕它的編譯器同意以下兩者之間存在歧義:
Bar<char>
到char
的隱式用戶定義轉換,然后是從char
到int
的隱式內置轉換,然后是顯式構造函數Foo(int)。 那么,哪個編譯器是對的? C ++ 14和C ++ 17之間標准的相關區別是什么?
附錄 :實際錯誤消息
這是gcc-8.2 -std=c++17
的錯誤。 gcc-7.2 -std=c++14
打印相同的錯誤:
<source>: In function 'Foo foo(Bar<char>)':
<source>:17:17: error: call of overloaded 'Foo(Bar<char>&)' is ambiguous
return (Foo)x;
^
<source>:3:14: note: candidate: 'Foo::Foo(int)'
explicit Foo(int ptr);
^~~
<source>:1:8: note: candidate: 'constexpr Foo::Foo(const Foo&)'
struct Foo
^~~
<source>:1:8: note: candidate: 'constexpr Foo::Foo(Foo&&)'
這是來自clang-7 -std=c++14
的錯誤( clang-7 -std=c++17
接受代碼):
<source>:17:12: error: ambiguous conversion for C-style cast from 'Bar<char>' to 'Foo'
return (Foo)x;
^~~~~~
<source>:1:8: note: candidate constructor (the implicit move constructor)
struct Foo
^
<source>:1:8: note: candidate constructor (the implicit copy constructor)
<source>:3:14: note: candidate constructor
explicit Foo(int ptr);
^
1 error generated.
這里有幾種力量在起作用。 為了理解發生了什么,讓我們來看看(Foo)x
應該引導我們的位置。 首先,在這種特殊情況下,c樣式轉換等同於static_cast
。 靜態強制轉換的語義是直接初始化結果對象。 由於結果對象是類類型, [dcl.init] /17.6.2告訴我們它的初始化如下:
否則,如果初始化是直接初始化,或者如果它是復制初始化,其中源類型的cv-nonqualified版本與目標類相同的類或派生類,則考慮構造函數。 枚舉適用的構造函數([over.match.ctor]),並通過重載決策選擇最佳構造函數。 調用所選的構造函數來初始化對象,初始化表達式或表達式列表作為其參數。 如果沒有構造函數適用,或者重載決策是不明確的,則初始化是錯誤的。
所以重載決議選擇Foo
的構造函數來調用。 如果重載解析失敗,程序就會形成錯誤。 在這種情況下,它應該不會失敗,即使我們有3個候選構造函數。 那些是Foo(int)
, Foo(Foo const&)
和Foo(Foo&&)
。
首先,我們需要將一個int
初始化為構造函數的參數,這意味着找到一個從Bar<char>
到int
的隱式轉換序列。 由於您從Bar<char>
給char
的用戶定義轉換運算符不是顯式的,我們可以從隱式對話序列Bar<char> -> char -> int
。
對於其他兩個構造函數,我們需要綁定對Foo
的引用。 但是,我們不能這樣做。 根據[over.match.ref] / 1 :
在[dcl.init.ref]中指定的條件下,引用可以直接綁定到glvalue或類prvalue,它是將轉換函數應用於初始化表達式的結果。 重載分辨率用於選擇要調用的轉換函數。 假設“cv1 T”是被初始化的引用的基礎類型,並且“cv S”是初始化表達式的類型,S是類類型,候選函數被選擇如下:
- 考慮S及其基類的轉換函數。 那些未隱藏在S中的非顯式轉換函數和yield類型“對cv2 T2的左值引用”(當初始化左值引用或對函數的右值引用時)或“cv2 T2”或“對cv2 T2的rvalue引用”(當初始化rvalue引用或對函數的左值引用,其中“cv1 T”與“cv2 T2”是參考兼容的([dcl.init.ref]),是候選函數。 對於直接初始化,那些未隱藏在S中的顯式轉換函數並且分別產生類型“對cv2 T2的左值引用”或“cv2 T2”或“對cv2 T2的rvalue引用”,其中T2與T的類型相同或者可以通過限定轉換([conv.qual])轉換為類型T,也是候選函數。
唯一可以產生類型為Foo
的glvalue或prvalue的轉換函數是您指定的顯式轉換函數模板的特化。 但是,由於函數參數的初始化不是直接初始化,我們不能考慮顯式轉換函數。 所以我們不能在重載決策中調用副本或移動構造函數。 只留下構造函數采用int
。 因此,重載決策是成功的,應該是它。
那么為什么有些編譯器會發現它不明確,或者調用模板化轉換運算符呢? 好吧,由於保證復制省略被引入標准,因此注意到( CWG問題2327 )用戶定義的轉換函數也應該有助於復制省略。 今天,根據標准的干信,他們沒有。 但我們真的很喜歡他們。 盡管有關如何完成的措辭仍在制定中,但似乎有些編制者已經開始嘗試實施它。
這就是你看到的實現。 這是延伸復制省略的反對力量,干擾了過載分辨率。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.