auto z = {1, 2, 3.0}; // ошибка: нельзя вывести тип элемента
Как это работает
Отдельно от механизма выведения типа auto
оператор {}
ведет себя предсказуемо, по крайней мере при инициализации обычных типов. При инициализации контейнеров наподобие std::vector
, std::list
и т.д. инициализатор с фигурными скобками будет соответствовать конструктору std::initializer_list
этого класса-контейнера. При этом он не может соответствовать неагрегированным конструкторам (таковыми являются обычные конструкторы, в отличие от тех, что принимают список инициализаторов).
std::vector
, например, предоставляет конкретный неагрегированный конструктор, заносящий в некоторое количество элементов одно и то же значение: std::vector<int> v (N, value)
. При записи std::vector<int> v {N, value}
выбирается конструктор initializer_list
, инициализирующий вектор с двумя элементами: N
и value
. Об этом следует помнить.
Есть интересное различие между оператором {}
и вызовом конструктора с помощью обычных скобок ()
. В первом случае не выполняется неявных преобразований типа: int x (1.2);
и int x = 1.2;
инициализируют переменную x
значением 1
, округлив в нижнюю сторону число с плавающей точкой и преобразовав его к типу int
. А вот выражение int x {1.2};
не скомпилируется, поскольку должно точно соответствовать типу конструктора.
Кто-то может поспорить о том, какой стиль инициализации является лучшим. Любители стиля с фигурными скобками говорят, что последние делают процесс явным, переменная инициализируется при вызове конструктора и эта строка кода ничего не инициализирует повторно. Более того, при использовании фигурных скобок
{}
будет выбран единственный подходящий конструктор, в то время как в момент применения обычных скобок ()
— ближайший похожий конструктор, а также выполнится преобразование типов.
Дополнительное правило, включенное в С++17, касается инициализации с выведением типа auto: несмотря на то что в C++11 тип переменной auto x{123};
(std::initializer_list<int>
с одним элементом) будет определен корректно, скорее всего, это не тот тип, который нужен. В С++17 та же переменная будет типа int
.
Основные правила:
□ в конструкции auto var_name {one_element};
переменная var_name
будет иметь тот же тип, что и one_element;
□ конструкция auto var_name {element1, element2,};
недействительна и не будет скомпилирована;
□ конструкция auto var_name = {element1, element2,};
будет иметь тип std::initializer_list<T>
, где T
— тип всех элементов списка.
В С++17 гораздо сложнее случайно определить список инициализаторов.
Попытка скомпилировать эти примеры в разных компиляторах в режиме C++11 или C++14 покажет, что одни компиляторы автоматически выводят тип
auto x {123};
как int
, а другие — как std::initializer_list<int>
. Подобный код может вызвать проблемы с переносимостью!
Разрешаем конструктору автоматически выводить полученный тип класса шаблона
Многие классы C++ обычно специализируются по типам, о чем легко догадаться по типам переменных, которые пользователь задействует при вызовах конструктора. Тем не менее до С++17 эти возможности не были стандартизированы. С++17 позволяет компилятору автоматически вывести типы шаблонов из вызовов конструктора.
Как это делается
Данную особенность очень удобно проиллюстрировать на примере создания экземпляров типа std::pair
и std::tuple
. Это можно сделать за один шаг:
std::pair my_pair (123, "abc"); // std::pair<int, const char*>
std::tuple my_tuple (123, 12.3, "abc"); // std::tuple<int, double, const char*>
Как это работает
Определим класс-пример, где автоматическое выведение типа шаблона будет выполняться на основе переданных значений:
template <typename T1, typename T2, typename T3>
class my_wrapper {
T1 t1;
T2 t2;
T3 t3;
public:
explicit my_wrapper(T1 t1_, T2 t2_, T3 t3_)
: t1{t1_}, t2{t2_}, t3{t3_}
{}
/* … */
};
О’кей, это всего лишь еще один класс шаблона. Вот как мы раньше создавали его объект (инстанцировали шаблон):
my_wrapper<int, double, const char *> wrapper {123, 1.23, "abc"};
Теперь же можно опустить специализацию шаблона:
my_wrapper wrapper {123, 1.23, "abc"};
До появления C++17 это было возможно только при реализации вспомогательной функции: