Подтвердить что ты не робот

Ошибка инициализации списка и ошибки при перегрузке конструктора initializer_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 как единственный аргумент, или как первый аргумент, если остальные аргументы имеют значение по умолчанию значения, проверяются и сопоставляются разрешением перегрузки против единственный аргумент типа std:: initializer_list

Если предыдущий этап не дает соответствия, все конструкторы T участвовать в разрешении перегрузки против множества аргументов, которые состоит из элементов списка бит-init, с ограничением что допускаются только не суживающиеся преобразования. Если этот этап создает явный конструктор как наилучшее совпадение для copy-list-initialization, компиляция не выполняется (обратите внимание, в простой copy-initialization, явные конструкторы вообще не рассматриваются)

Поскольку сужение конверсий недопустимо, я ожидаю, что шаг разрешения перегрузки не будет соответствовать конструктору A(std::initializer_list<int>) и вместо этого будет соответствовать A(int, double). Например, смена A(std::initializer_list<int>) на A(std::initializer_list<std::string>) компилируется как с clang35, так и g++48 и печатает

A::A(int, double)

как ожидалось.

4b9b3361

Ответ 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 выбирает предупреждение. Оба компилятора соответствуют.