1
#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; 
} 

(Этот вопрос является продолжением до this.)Почему этот конструктор initializer_list является жизнеспособной перегрузкой?

выше программа не компилируется с 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)?

+1

Как применяется 8.5.1 для 'int t = 1.0;'? Это не агрегированная инициализация, не так ли? – Columbo

+1

В сообщении было указано, что вы привязаны к тем конструкторам, которые принимают список инициализаторов, сильно одобрены. Таким образом, компилятор выбирает конструктор списка инициализаторов, а затем пытается преобразовать и поскольку он сужает компиляцию. – NathanOliver

+0

@Columbo Извините, я не понял. Кроме того, для '8.5.1' я не вставлял всю стандартную цитату, но, поскольку вы упоминаете агрегатную инициализацию, я думаю, это достаточно ясно. – Pradhan

ответ

3

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

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; } 

Demo.

+0

А, ну, похоже, это объясняет. Переходите через эти части, чтобы я понял. Как вы думаете, стандарт указывает это на этот, казалось бы, запутанный путь? Почему бы просто не убедиться, что в нем нет жизнеспособной перегрузки? Следует ли избегать усложнения инициализации агрегата? IOW им пришлось бы различать « = {1, 1.0}» и ' = {1, 1.0}'? – Pradhan

+1

@Pradhan Я понятия не имею, tbh. Кажется, что либо мы пропускаем некоторые угловые шкафы, либо раздел перегоняется. – Columbo

+0

Я думаю, было бы странно, если бы используемый конструктор зависел от значения аргумента (для преобразований от целочисленного к целочисленному типу, где источник является постоянным выражением, например). – dyp

Смежные вопросы