2015-01-21 39 views
11

超載分辨率低於失敗clang35 -std=c++11編譯:列表初始化和失敗的initializer_list構造

#include <iostream> 
#include <string> 
#include <initializer_list> 

class A 
{ 
public: 
    A(int, bool) { std::cout << __PRETTY_FUNCTION__ << std::endl; } 
    A(int, double) { std::cout << __PRETTY_FUNCTION__ << std::endl; } 
    A(std::initializer_list<int>) { std::cout << __PRETTY_FUNCTION__ << std::endl; } 
}; 

int main() 
{ 
    A a1 = {1, 1.0}; 
    return 0; 
} 

錯誤

init.cpp:15:14: error: type 'double' cannot be narrowed to 'int' in initializer list [-Wc++11-narrowing] 
    A a1 = {1, 1.0}; 
      ^~~ 
init.cpp:15:14: note: insert an explicit cast to silence this issue 
    A a1 = {1, 1.0}; 
      ^~~ 
      static_cast<int>() 

OTOH,它告誡,縮小並編譯g++48 -std=c++11

init.cpp: In function ‘int main()’: 
init.cpp:15:17: warning: narrowing conversion of ‘1.0e+0’ from ‘double’ to ‘int’ inside { } [-Wnarrowing] 
    A a1 = {1, 1.0}; 
       ^
init.cpp:15:17: warning: narrowing conversion of ‘1.0e+0’ from ‘double’ to ‘int’ inside { } [-Wnarrowing] 

和產生的結果

A::A(std::initializer_list<int>) 

這兩種行爲都有意義嗎?從cppreference

All constructors that take std::initializer_list as the only argument, or as the first argument if the remaining arguments have default values, are examined, and matched by overload resolution against a single argument of type std::initializer_list

If the previous stage does not produce a match, all constructors of T participate in overload resolution against the set of arguments that consists of the elements of the braced-init-list, with the restriction that only non-narrowing conversions are allowed. If this stage produces an explicit constructor as the best match for a copy-list-initialization, compilation fails (note, in simple copy-initialization, explicit constructors are not considered at all)

引述由於收縮轉換是不允許的,我希望重載解析步驟不匹配A(std::initializer_list<int>)構造,而是匹配A(int, double)之一。例如,改變A(std::initializer_list<int>)A(std::initializer_list<std::string>)clang35g++48並打印

A::A(int, double) 

如預期編譯。

+0

大概你的意思是鏗鏘3.5。你命名這個二進制文件並不是真正有用的:)'mv clang25 clang35'「oops」 – 2015-02-07 04:39:39

+0

你是對的:)這是團隊在工作中維護構建系統時使用的版本控制慣例,我從來沒有給過它一個想法。 – Pradhan 2015-02-07 07:00:43

回答

11

這種行爲是有道理的。斯科特邁爾斯具有例如幾乎完全一樣本中有效的現代C++(以原強調):

If, however, one or more constructors declare a parameter of type std::initializer_list , calls using the braced initialization syntax strongly prefer the overloads taking std;:initializer_list s. Strongly. If there's any way for compilers to construe a call using a braced initializer to be a constructor taking a std::initializer_list , compilers will employ that interpretation.

實施例使用這個類:

class Widget { 
public: 
    Widget(int, bool); 
    Widget(int, double); 
    Widget(std::initializer_list<long double>); 
}; 

Widget w1(10, true); // calls first ctor 
Widget w2{10, true}; // calls std::initializer_list ctor 
Widget w3(10, 5.0); // calls second ctor 
Widget w4{10, 5.0}; // calls std::initializer_list ctor 

這兩個呼叫撥打initializer_list構造函數,即使它們涉及轉換兩個論點 - 儘管其他構造函數都是完美匹配。

此外:

Compilers' determination to match braced initializers with constructors taking std::initializer_list s is so strong, it prevails even if the best-match std::initializer_list constructor can't be called. For example:

class Widget { 
public: 
    Widget(int, bool); // as before 
    Widget(int, double); // as before 
    Widget(std::initializer_list<bool>); // now bool 
}; 

Widget w{10, 5.0}; // error! requires narrowing conversions 

兩種編譯器選擇正確的過載(在initializer_list之一) - 我們可以看到從標準(§13.3.1.7)要求:

When objects of non-aggregate class type T are list-initialized (8.5.4), overload resolution selects the constructor in two phases:

(1.1) — Initially, the candidate functions are the initializer-list constructors (8.5.4) of the class T and the argument list consists of the initializer list as a single argument.
(1.2) — If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all the constructors of the class T and the argument list consists of the elements of the initializer list.

但調用該特定構造函數涉及縮小。在8.5.1中:

If the initializer-clause is an expression and a narrowing conversion (8.5.4) is required to convert the expression, the program is ill-formed.

所以這個程序是不合格的。在這種情況下,當gcc選擇發出警告時,clang選擇拋出錯誤。兩個編譯器都符合要求。

+0

哪種行爲有意義? clang和gcc不同意 - clang 3.5無法編譯,而g ++ 4.8.3即使在縮小轉換的情況下也會選擇'initializer_list'過載。上面的例子沒有說明任何困難 - 轉換沒有縮小,因此挑選initilalizer_list過載並不奇怪。 – Pradhan 2015-01-21 02:09:18

+4

我們可以補充說這個_strong偏好_很棒,因爲那樣你可以'std :: vector {「hello」,「world」}'因爲編譯器_努力嘗試並將初始化程序列表看作是std :: string類型對象的初始化程序。否則,我們會被永久卡住寫入'std :: vector {std :: string {「hello」},std :: string {「world」}}'。 _想象一下當你嘗試初始化某些東西時的痛苦[更簡單](http://stackoverflow.com/a/28036210)_。 – sehe 2015-01-21 02:12:01

+0

@Pradhan在書中增加縮小范例 – Barry 2015-01-21 02:17:29