2012-02-06 82 views
36

在C++標準,§13.3.1.7[over.match.list],以下陳述:如果copy-list-initialization允許顯式構造函數,會出現什麼問題?

在copy-列表初始化,如果選擇一個explicit構造,是形成不良的初始化。

這就是爲什麼我們不能做,例如其原因,是這樣的:

struct foo { 
    // explicit because it can be called with one argument 
    explicit foo(std::string s, int x = 0); 
private: 
    // ... 
}; 

void f(foo x); 

f({ "answer", 42 }); 

(注意,這裏發生的事情是不是轉換,並且它不會是一個即使構造是「隱」,這是直接使用其構造一個foo對象的初始化。除了std::string,沒有轉換這裏。)

這個S eems對我來說非常好。沒有辦法隱式轉換會咬我。

如果{ "answer", 42 }可以初始化別的東西,編譯器不會背叛我,做錯事:

struct bar { 
    // explicit because it can be called with one argument 
    explicit bar(std::string s, int x = 0); 
private: 
    // ... 
}; 

void f(foo x); 
void f(bar x); 

f({ "answer", 42 }); // error: ambiguous call 

沒有任何問題:電話是模糊的,代碼將無法編譯,我必須明確地選擇超載。

f(bar { "answer", 42 }); // ok 

由於禁止條款明確規定,我有這種感覺,我在這裏失去了一些東西。據我所見,列表初始化選擇顯式構造函數對我來說似乎不是一個問題:通過使用列表初始化語法,程序員已經表達了做某種「轉換」的願望。

什麼可能出錯?我錯過了什麼?

+2

我不確定,但我認爲這很邏輯。調用f({「answer」,42}),你可能永遠不會知道你傳遞了一個foo,並且你試圖使用的構造函數是明確的,它強制顯式轉換。 – Geoffroy 2012-02-06 07:55:46

+1

@Geoffroy:如果別的東西可以從'{「answer」,42}'傳遞,重載決議將是不明確的,因此迫使我明確類型。 – 2012-02-06 07:58:30

+0

'通過使用列表初始化語法,程序員已經表達了進行某種轉換的願望:但不是爲了foo。如果'f()'有另一個接受初始化列表的重載,該怎麼辦? – sehe 2012-02-06 07:59:31

回答

23

從概念上講,複製列表初始化是將複合值轉換爲目標類型。提出措辭和解釋理由的論文已經在「副本列表初始化」中考慮過「複製」這個詞,因爲它沒有真正傳達它背後的真正原理。但是爲了與現有的措詞兼容而保留。 A {10, 20} pair/tuple值不應該能夠複製初始化String(int size, int reserve),因爲字符串不是一對。

顯式構造函數被認爲是被禁止使用的。這在下列情況下有意義

struct String { 
    explicit String(int size); 
    String(char const *value); 
}; 

String s = { 0 }; 

0不傳達字符串的值。因此,這將導致因爲兩個構造被認爲是一個錯誤,而是選擇了explicit構造函數,而不是0被視爲一個空指針常量。

不幸的是,這也發生在重載跨越這形成不良的,因爲太多的不確定性的功能

void print(String s); 
void print(std::vector<int> numbers); 

int main() { print({10}); } 

。在C++ 11發佈之前,有些人(包括我)認爲這是不幸的,但是沒有提出一篇文章提出改變(就我所知)。

+0

太棒了!最後*一個錯誤的例子*。不幸的是,這意味着這不能很容易地修復:(在上一個例子中,我的意思是'void f(int i)') – 2012-02-06 10:32:41

+0

@RMartin no我的意思是「Int」,如果它是「int」那麼它將會是一個精確的匹配,並且會被其他用戶定義的轉換選中。「Int」是一個簡單的包裝,就像着名的SafeInt類一樣。 – 2012-02-06 10:38:28

+0

啊,我現在明白了,只有構造函數實際上它是真正適用的:'Int',但是由於'String'中的'explicit'被認爲是無論如何,這個調用是不明確的,謝謝 – 2012-02-06 10:41:49

2

據我所知,關鍵字顯式的目的是拒絕用此構造函數隱式轉換。

所以你問爲什麼顯式構造函數不能用於隱式轉換?很明顯,因爲該構造函數的作者明確否認使用關鍵字顯式。您發佈的標準中的引用只是指出顯式關鍵字也適用於初始化程序列表(不僅適用於某些類型的簡單值)。

地址:

更準確地說:有一些構造函數中使用的關鍵字明確的目的是使絕對清楚地表明該構造函數在某個地方使用(即迫使所有的代碼來調用這個構造函數明確)。

而像f({a,b})這樣的IMO語句,當f是該函數的名稱與顯式構造函數調用無關。這是絕對不清楚(和上下文相關的)在這裏使用了哪個構造函數(和什麼類型),例如,它取決於存在的功能過載。

在另一方面像f(SomeType(a,b))是完全不同的事情 - 這是絕對清楚的是,我們使用SomeType類型有兩個參數的構造函數a,b和我們使用的功能f過載,這將是最好的,接受單個參數類型爲SomeType

所以一些構造是一樣f({a,b})隱式使用OK和其他要求其使用的事實是絕對清楚的讀者,這就是爲什麼我們宣佈他們明確

ADD2:

我的觀點是:有時,它絕對是有道理的聲明構造明確,即使沒有什麼可能出錯。海事組織是否構造是明確的更多的是它的邏輯問題,而不是任何形式的警告。

E.g.

double x = 2; // looks absolutely natural 
std::complex<double> x1 = 3; // also looks absolutely natural 
std::complex<double> x2 = { 5, 1 }; // also looks absolutely natural 

std::vector< std::set<std::string> > seq1 = 7; // looks like nonsense 
std::string str = some_allocator; // also looks stupid 
+0

該代碼不涉及轉換(除了'std :: string')。 – 2012-02-06 09:30:20

+1

@ R. Martinho Fernandes:好的。好。如果它更好,我會用_cast_這個詞。海事組織這幾乎類似於初始化一個類型的變量與另一種類型的值,這正是什麼隱式轉換(並且它是直接在同一時間使用構造函數)。 – 2012-02-06 10:43:29

+0

它也不是演員。這與隱式轉換之間的區別在於,如果沒有爲它編寫任何*代碼,就會發生隱式轉換。爲此,你編寫代碼來初始化一個對象('{}')。當您查看代碼時很清楚發生了什麼。 – 2012-02-06 10:47:04

1

是不是因爲「明確」在那裏阻止隱式轉換,而你要求它做一個隱式轉換?

你會問這個問題,如果你已經指定了一個參數構造函數的結構?

+0

你的意思是,像這樣的'f({42})'?它會出什麼問題?另外**我的代碼中沒有演員**。 – 2012-02-06 09:22:04

2

本聲明:

在副本列表初始化,如果選擇的是explicit構造,是形成不良的初始化。

意味着很多事情。其中,這意味着它必須在顯式構造函數中看到。畢竟,如果它不能查看它,它就不能選擇一個顯式的構造函數。當它尋找候選人將支撐列表轉換成時,它必須從所有候選人中選擇。即使是那些將後來被發現是非法的。

如果重載分辨率導致多個函數同樣可行,則會導致需要手動用戶干預的模糊呼叫。

相關問題