簡體   English   中英

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()

拒絕它的編譯器同意以下兩者之間存在歧義:

  1. 顯式轉換函數Bar :: operator int()
  2. 首先使用從Bar<char>char的隱式用戶定義轉換,然后是從charint的隱式內置轉換,然后是顯式構造函數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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM