Почему автоматическое вычитание и вычитание типа шаблона отличаются для инициализаторов в фигурных скобках?

Я понимаю, что, учитывая инициализированный скобками, auto выведет тип std::initializer_list, в то время как вывод типа шаблона не удастся:

auto var = { 1, 2, 3 };   // type deduced as std::initializer_list<int>

template<class T> void f(T parameter);

f({ 1, 2, 3 });          // doesn't compile; type deduction fails

Я даже знаю, где это указано в стандарте C ++ 11: 14.8.2.5/5 bullet 5:

[Это не выводимый контекст, если программа имеет] Параметр функции, для которого связанный аргумент представляет собой список инициализатора (8.5.4), но параметр
не имеет std :: initializer_list или ссылки на возможно cv-квалифицированный std :: initializer_list
тип. [ Пример:

шаблон пустоты g (T);

г ({1,2,3}); // ошибка: аргумент не выводится для T

конец примера ]

То, что я не знаю или не понимаю, Зачем эта разница в типе поведения вывода существует. Спецификация на C ++ 14 CD такая же, как и в C ++ 11, поэтому предположительно комитет по стандартизации не рассматривает поведение C ++ 11 как дефект.

Кто-нибудь знает почему auto выводит тип для фигурного инициализатора, но шаблоны не разрешены? Хотя спекулятивные объяснения формы «это может быть причиной» интересны, меня особенно интересуют объяснения людей, которые знать почему стандарт был написан именно так.

18

Решение

Есть две важные причины, по которым шаблоны не должны делать никаких выводов (две, которые я помню при обсуждении с ответственным парнем)

  • Обеспокоенность по поводу будущих расширений языка (есть несколько значений, которые вы могли бы придумать — что, если бы мы хотели ввести идеальную переадресацию для аргументов функции фигурного списка инициализации?)

  • Иногда фигурные скобки могут корректно инициализировать параметр функции, который зависит

template<typename T>
void assign(T &d, const T& s);
int main() {
vector<int> v;
assign(v, { 1, 2, 3 });
}

Если T будет выведен на правой стороне initializer_list<int> но с левой стороны vector<int>, это не сработает из-за противоречивого вывода аргумента.

Вычет за auto в initializer_list<T> является спорным. Существует предложение для C ++ — после 14 удалить его (и запретить инициализацию с помощью { } или же {a, b}и сделать {a} выводить на тип a).

13

Другие решения

Причина описана в N2640:

Список {} не может быть выведен против параметра простого типа T, Например:

template<class T> void count(T); // (1).
struct Dimensions { Dimensions(int, int); };
size_t count(Dimensions); // (2).
size_t n = count({1, 2}); // Calls (2); deduction doesn't
// succeed for (1).

Другой пример:

template<class T>
void inc(T, int); // (1)
template<class T>
void inc(std::initializer_list<T>, long); // (2)
inc({1, 2, 3}, 3); // Calls (2). (If deduction had succeeded
// for (1), (1) would have been called — a
// surprise.)

С другой стороны, способность выводить initializer_list<X> за T является привлекательным для
разрешать:

auto x = { 1, 1, 2, 3, 5 };
f(x);
g(x);

что было сочтено желательным поведением с самого начала дискуссий РГЭ о
списки инициализаторов.

Вместо того, чтобы придумать умное правило удержания для типа параметра T в сочетании с {} -списком (опция, которую мы использовали в более ранних набросках и черновиках этой статьи), теперь мы предпочитаем обрабатывать это с особым случаем для «автоматического» вывода переменной, когда инициализатор является {} -списком. То есть, для конкретного случая переменной, объявленной со спецификатором типа «auto» и инициализатором {} -list, «auto» выводится как для функции f(initializer_list<T>) а не как для функции f(T),

В заключение, проблема заключается в том, что если мы позволим {} -листу выводить параметр простого типа Tтогда функция с параметром T будет иметь очень высокий приоритет при разрешении перегрузки, что может привести к поведению в проводном режиме (как в приведенных выше примерах).

1

Прежде всего, это «умозрительные объяснения формы« это может быть причиной »», как вы это называете.

{1,2,3} это не только std::initializer_list<int> но также позволяют инициализировать типы без конструктора. Например:

#include <initializer_list>

struct x{
int a,b,c;
};

void f(x){

}
int main() {
f({1,2,3});
}

правильный код Чтобы показать, что это не так initializer_list давайте посмотрим на следующий код:

#include <initializer_list>

struct x{int a,b,c;};

void f(x){

}
int main() {
auto il = {1, 2, 3};
f(il);
}

Ошибка:

prog.cpp: In function ‘int main()’:
prog.cpp:10:9: error: could not convert ‘il’ from ‘std::initializer_list<int>’ to ‘x’

А теперь вопрос «В чем разница?»

в auto x = {1, 2, 3}; Код это нормально, чтобы определить тип, потому что кодер явно сказал: «Не важно, какой это тип», используя auto

Хотя в случае с шаблоном функции он может быть уверен, что использует другой тип. И это хорошо, чтобы предотвратить ошибки в неоднозначных случаях (это не похоже на стиль C ++, насквозь).

Особенно плохо это будет в случае, когда была 1 функция f(x) и тогда это было изменено на шаблон один. Программист написал, чтобы использовать его как xи после добавления новой функции для другого типа она слегка меняется, чтобы вызвать совершенно другую.

0