Ответ 1
Поведение имеет смысл. Скотт Мейерс имеет пример, почти такой же, как в Effective Modern С++ (акцент в оригинале):
Если, однако, один или несколько конструкторов объявляют параметр типа
std::initializer_list
, вызовы с использованием синтаксиса с принудительной инициализацией сильно предпочитают перегрузки, принимающие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, хотя они включают преобразование аргументов BOTH, и даже если другие конструкторы являются идеальными совпадениями.
Далее
Определение компиляторов для согласованных инициализаторов с конструкторами, принимающими
std::initializer_list
, настолько сильное, что оно преобладает, даже если конструктор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), разрешение перегрузки выбирает конструктор в две фазы:(1.1) - Изначально функции-кандидаты являются конструкторами-списками инициализаторов (8.5.4) класса
T
и Список аргументов состоит из списка инициализаторов как одного аргумента.
(1.2). Если не найдено жизнеспособного конструктора-списка инициализаторов, снова выполняется разрешение перегрузки, где Функции-кандидаты - все конструкторы классаT
, а список аргументов состоит из элементов списка инициализаторов.
Но вызов этого конкретного конструктора предполагает сужение. В 8.5.1:
Если предложение initializer - выражение и для преобразования выражения требуется суживающее преобразование (8.5.4), программа плохо сформирована.
Таким образом, программа плохо сформирована. В этом случае clang выбирает ошибку, а gcc выбирает предупреждение. Оба компилятора соответствуют.