Почему этот конструктор 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;
}

(Этот вопрос является продолжением этого.)

Вышеупомянутая программа не скомпилируется с clang35 -std=c++11

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>( )

в то время как 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>)

Мой вопрос в том, должна ли A::A(std::initializer_list<int>) быть жизнеспособной перегрузкой. Ниже приведены стандартные цитаты, которые, я думаю, подразумевают, что перегрузка initializer_list не должна быть жизнеспособной.

От 13.3.2 [over.match.viable]

Во-вторых, чтобы F была жизнеспособной функцией, для каждого аргумента должна существовать последовательность неявного преобразования, которая преобразует этот аргумент в соответствующий параметр F.

От 4 [conv]

Выражение e может быть неявно преобразовано в тип T тогда и только тогда, когда объявление T t=e; корректно, для некоторой придуманной временной переменной t.

От 8.5.1 [dcl.init.aggr]

Если предложение-инициализатор является выражением, и для преобразования выражения требуется сужающее преобразование, программа имеет неправильный формат.

Использование 8.5.1 и 4, так как следующее неправильно сформировано

std::initializer_list<int> e = {1, 1.0};

{1, 1.0} не может быть неявно преобразовано в std::initializer_list<int>.

Используя цитату из 13.3.2, не должно ли это означать, что A::A(std::initializer_list<int>) не является жизнеспособной функцией при разрешении перегрузки для A a1 = {1, 1.0};? Не найдя жизнеспособных конструкторов initializer_list, не должен ли этот оператор выбрать A::A(int, double)?


person Pradhan    schedule 21.01.2015    source источник
comment
Как 8.5.1 применяется для int t = 1.0;? Это не агрегатная инициализация, не так ли?   -  person Columbo    schedule 21.01.2015
comment
В сообщении, на которое вы ссылались, было указано, что конструкторы, которые принимают список инициализаторов, настоятельно рекомендуются. Таким образом, компилятор выбирает конструктор списка инициализаторов, а затем пытается преобразовать его, и, поскольку он сужает компиляцию, происходит сбой.   -  person NathanOliver    schedule 21.01.2015
comment
@Columbo Извините, я не понял вашей точки зрения. Кроме того, для 8.5.1 я не вставил всю стандартную цитату, но поскольку вы упомянули агрегатную инициализацию, я думаю, это достаточно ясно.   -  person Pradhan    schedule 21.01.2015
comment
@NathanOliver Да, но сильное предпочтение Скотт Мейерс устанавливает стандарт на простом английском языке. Я спрашиваю, как это следует из стандарта.   -  person Pradhan    schedule 21.01.2015
comment
@Pradhan Я опубликовал ответ, чтобы прояснить свою точку зрения.   -  person Columbo    schedule 21.01.2015


Ответы (1)


Я считаю, что проблема в вашем анализе заключается в том, что утверждение

int t = 1.0;

действительно правильно сформирован - очевидно, существует неявное преобразование из double в int. [over.ics.list]/4 также описывает это:

В противном случае, если тип параметра std::initializer_list<X> и все элементы списка инициализатора могут быть неявно преобразованы в X, последовательность неявного преобразования является наихудшим преобразованием, необходимым для преобразования элемента списка в X, или если в списке инициализатора нет элементов , преобразование личности.

Каждый элемент из списка инициализаторов может быть неявно преобразован в int, поэтому конструктор является жизнеспособным и выбранным. Однако только после того, как он выбран, все это приводит к серьезным ошибкам, [dcl.init.list]/(3.6):

Перечисляются применимые конструкторы, и лучший из них выбирается с помощью разрешения перегрузки (13.3, 13.3.1.7). Если для преобразования любого из аргументов требуется сужающее преобразование (см. ниже), программа имеет неправильный формат.

Как видите, вызываемый конструктор определяется до выполнения проверки сужения. Другими словами, жизнеспособность конструктора списка инициализаторов не зависит от сужения каких-либо аргументов.
Таким образом, код должен иметь неправильный формат.

Один из способов получить желаемое поведение — использовать шаблон конструктора с SFINAE.

template <typename T, typename=std::enable_if_t<std::is_same<int, T>{}>>
A(std::initializer_list<T>) { std::cout << __PRETTY_FUNCTION__ << std::endl; }

Демо.

person Columbo    schedule 21.01.2015
comment
Ах, хорошо, это, кажется, объясняет это. Просматриваю эти части, чтобы убедиться, что я понимаю. Как вы думаете, что стандарт определяет это таким, казалось бы, запутанным образом? Почему бы просто не убедиться, что в нем нет жизнеспособной перегрузки? Чтобы избежать усложнения агрегатной инициализации? IOW им придется различать <some pod> = {1, 1.0} и <some init_list> = {1, 1.0}? - person Pradhan; 21.01.2015
comment
@Pradhan Я понятия не имею. Кажется, что либо мы упускаем какие-то краеугольные случаи, либо раздел поврежден. - person Columbo; 21.01.2015
comment
Я думаю, было бы странно, если бы используемый конструктор зависел от значения аргумента (например, для преобразования из целочисленного типа в целочисленный, где источником является константное выражение). - person dyp; 21.01.2015