📚 Hub Books: Онлайн-чтение книгРазная литератураC++17 STL Стандартная библиотека шаблонов - Яцек Галовиц

C++17 STL Стандартная библиотека шаблонов - Яцек Галовиц

Шрифт:

-
+

Интервал:

-
+
1 2 3 4 5 6 7 8 9 10 ... 121
Перейти на страницу:
объединяли данный синтаксис с типом переменных auto, был высок шанс выразить не то, что вам нужно. В C++17 появился улучшенный набор правил инициализатора. В следующем примере вы увидите, как грамотно инициализировать переменные в С++17 и какой синтаксис при этом использовать.

Как это делается

Переменные инициализируются в один прием. При использовании синтаксиса инициализатора могут возникнуть две разные ситуации.

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, принимает произвольное количество параметров и суммирует их с помощью

1 2 3 4 5 6 7 8 9 10 ... 121
Перейти на страницу:

Комментарии

Обратите внимание, что комментарий должен быть не короче 20 символов. Покажите уважение к себе и другим пользователям!

Никто еще не прокомментировал. Хотите быть первым, кто выскажется?