List-инициализация и ошибка разрешения перегрузки конструктора списка инициализаторов
ниже не удается скомпилировать с clang35 -std=c++11
:
#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
все конструкторы, которые принимают std:: initializer_list в качестве единственного аргумента, или как первый аргумент, если остальные аргументы по умолчанию значения, рассматриваются и сопоставляются разрешением перегрузки по отношению к a единственный аргумент типа std::initializer_list
Если предыдущий этап не дает совпадения, все конструкторы T участвовать в разрешении перегрузки против набора аргументов, которые состоит из элементов braced-init-list, с ограничением только не сужающие преобразования. Если этот этап создает явный конструктор как наилучшее соответствие для copy-list-инициализация, компиляция не выполняется (примечание, в простой copy-инициализация, явные конструкторы вообще не рассматриваются)
поскольку сужение конверсий не допускается, я ожидаю, что шаг разрешения перегрузки не будет соответствовать A(std::initializer_list<int>)
конструктор и вместо того, чтобы соответствовать A(int, double)
один. Например, изменение A(std::initializer_list<int>)
to A(std::initializer_list<std::string>)
составляет с clang35
и g++48
и печать
A::A(int, double)
как и ожидалось.
1 ответов
поведение имеет смысл. У Скотта Мейерса есть пример почти точно такой же в эффективном современном C++ (акцент в оригинале):
Если, однако, один или несколько конструкторов объявлять параметр типа
std::initializer_list
, вызовы с использованием синтаксиса инициализации braced сильно предпочитают перегрузки, принимающиеstd;:initializer_list
s. сильно. Если есть в любом случае для компиляторов для интерпретации вызова с использованием Связного инициализатора в качестве конструктора, принимающегоstd::initializer_list
компиляторы будут использовать эту интерпретацию.
пример использования этого класса:
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
ctor, хотя они включают преобразование обоих аргументов - и даже если другие конструкторы являются идеальными совпадениями.
кроме того:
определение компиляторов для сопоставления связанных инициализаторов с конструкторами, принимающими
std::initializer_list
s настолько сильно, оно преобладает даже если Бест-спичкаstd::initializer_list
конструктор не может быть вызван. Например: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
one) - который, как мы видим, требуется от стандарта (§13.3.1.7):
когда объекты неагрегатного типа класса
T
список инициализации (8.5.4), разрешение перегрузки выбирает конструктор в два этапа:--13-->(1.1) - первоначально функции-кандидаты являются конструкторами списка инициализаторов (8.5.4) класс!--9--> и список аргументов состоит из списка инициализаторов в виде одного аргумента.
(1.2) - если жизнеспособный конструктор инициализатора-списка не найден, разрешение перегрузки выполняется снова, где функции-кандидаты - это все конструкторы классаT
и список аргументов состоит из элементов из списка инициализаторов.
но вызов конкретного конструктора включает в себя сужение. В 8.5.1:
если the инициализатор-п. это выражение и для преобразования выражения требуется сужающее преобразование (8.5.4), программа плохо сформирована.
таким образом, программа плохо сформирована. В этом случае clang решает выдать ошибку, в то время как gcc решает выдать предупреждение. Оба компилятора соответствуют.