C++17 STL Стандартная библиотека шаблонов - Яцек Галовиц
Как это делается
Переменные инициализируются в один прием. При использовании синтаксиса инициализатора могут возникнуть две разные ситуации.
1. Применение синтаксиса инициализатора с фигурными скобками без выведения типа auto:
// Три идентичных способа инициализировать переменную типа int:
int x1 = 1;
int x2 {1};
int x3 (1);
std::vector<int> v1 {1, 2, 3};
// Вектор, содержащий три переменные типа int: 1, 2, 3
std::vector<int> v2 = {1, 2, 3}; // Такой же вектор
std::vector<int> v3 (10, 20);
// Вектор, содержащий десять переменных типа int,
// каждая из которых имеет значение 20
2. Использование синтаксиса инициализатора с фигурными скобками с выведением типа auto:
auto v {1}; // v имеет тип int
auto w {1, 2}; // ошибка: при автоматическом выведении типа
// непосредственная инициализация разрешена
// только одиночными элементами! (нововведение)
auto x = {1}; // x имеет тип std::initializer_list<int>
auto y = {1, 2}; // y имеет тип std::initializer_list<int>
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 это было возможно только при реализации вспомогательной функции:
my_wrapper<T1, T2, T3> make_wrapper(T1 t1, T2 t2, T3 t3)
{
return {t1, t2, t3};
}
Используя подобные вспомогательные функции, можно было добиться такого же эффекта:
auto wrapper (make_wrapper(123, 1.23, "abc"));
STL предоставляет множество аналогичных инструментов: std::make_shared, std::make_unique, std::make_tuple и т.д. В C++17 эти функции могут считаться устаревшими. Но, конечно, они все еще будут работать для обеспечения обратной совместимости.
Дополнительная информация
Из данного примера мы узнали о неявном выведении типа шаблона. Однако в некоторых случаях на этот способ нельзя полагаться. Рассмотрим следующий класс-пример:
template <typename T>
struct sum {
T value;
template <typename ... Ts>
sum(Ts&& ... values) : value{(values + ...)} {}
};
Эта структура, sum, принимает произвольное количество параметров и суммирует их с помощью
Откройте для себя мир чтения на siteknig.com - месте, где каждая книга оживает прямо в браузере. Здесь вас уже ждёт произведение C++17 STL Стандартная библиотека шаблонов - Яцек Галовиц, относящееся к жанру Программирование. Никаких регистраций, никаких преград - только вы и история, доступная в полном формате. Наш литературный портал создан для тех, кто любит комфорт: хотите читать с телефона - пожалуйста; предпочитаете ноутбук - идеально! Все книги открываются моментально и представлены полностью, без сокращений и скрытых страниц. Каталог жанров поможет вам быстро найти что-то по настроению: увлекательный роман, динамичное фэнтези, глубокую классику или лёгкое чтение перед сном. Мы ежедневно расширяем библиотеку, добавляя новые произведения, чтобы вам всегда было что открыть "на потом". Сегодня на siteknig.com доступно более 200000 книг - и каждая готова стать вашей новой любимой. Просто выбирайте, открывайте и наслаждайтесь чтением там, где вам удобно.


