В предыдущем видео мы с вами познакомились с порождающими функциями, которые позволяют переложить на компилятор вывод шаблонных типов при инстанцировании шаблонов класса. На самом деле это не единственный способ это сделать, есть еще один и он появился с самым последним принятым на данный момент стандарте языка С++, это С++ 17. И чтобы вас им воспользоваться, нужно в первую очередь настроить свою среду разработки так, чтобы она компилировала код в соответствии со стандартом С++ 17. Как это сделать? В первую очередь нужно убедиться, что у вас стоит компилятор GCC версии не младше седьмой. Как это сделать? Идем в консоль, у меня открыта консоль, здесь вводим g++ --version, выполняем эту команду и видим здесь вот версия компиляторa, у меня это 7.2.0. Вот чтобы пользоваться С++ 17, версия компилятора должна быть не младше седьмой. Кроме того, в самой ideclips нужно убедиться, что проект собирается с использованием самого свежего стандарта. Для этого нужно сделать следующее. Идем в свойства проекта правой кнопкой, здесь выбираем properties и здесь нас интересует С++ build, в нем пункт settings. Вот я сделаю покрупнее, вот этот вот C++ build, здесь settings, и в открывшейся вкладке с settings нас интересует вот это вот тот пункт, dialect. Нужно открыть его. Причем важно, чтобы это был именно С++ dialect, не вот здесь, внизу, где С, а именно С++ dialect. И вот здесь, вверху должно быть пусто, а в поле other dialect flags нужно написать -std=c++1z и именно этот флаг будет передан компилятору и компилятор будет собирать ваш код с использованием самого свежего стандарта С++ 17. В данном случае нужно писать Z, а не 17, потому что, когда работая над этим стандартом еще до конца не знали, в каком году его удастся принять и поэтому использовали вот такой вот ключ с использованием буквы z, а не уже конкретные цифры. Вот значит, таким образом нужно настроить свою среду, чтобы она использовала С++ 17 и чтобы пользоваться тем способом вывода типов, о котором мы сейчас поговорим. Собственно способ этот заключается вот в чем, если в классе есть конструктор позволяющий определить тип шаблона, то компилятор сам выводит этот тип. И давайте рассмотрим пример на экране. У нас есть шаблон класса widget, в котором есть конструктор, принимающий значение value типа T. В данном случае Т это как раз наш шаблонный параметр. Так вот у нас есть конструктор, который принимает значение типа Т, поэтому компилятор по этому конструктору может сам вывести тип, и мы можем написать то, что у меня представлено ниже. Мы можем объявить переменную w_int и проинициализировать ее пятеркой, при этом в качестве ее типа мы просто пишем widget, мы не указываем конкретный тип, мы просто пишем widget, шаблон, шаблон widget w_int(5). Компилятор берёт эту пятерку, смотрит какие есть конструкторы в шаблоне widget, видит, что есть конструктор, принимающий value типа Т и он понимает, что пять имеет тип int, поэтому мы хотим инстанцировать шаблон widget с помощью типа int и он создает класс widget<int>. Ну и в примере ниже мы создаем примерную w_char, передавать его символ а и точно так также компилятор по этому конструктору понимает, что нужно инстанцировать класс widget<char>, при этом нам не нужно явно вводить этот тип, мы просто написали widget, сказали, компилятор создай мне класс вот из этого шаблона и вот значит вот тебе параметр, чтобы ты догадался с какими типами его инстанцировать. Вот так в общем устроен этот способ. Давайте посмотрим как это работает. Ну, самый простой пример это пара. Допустим у нас есть пара, от int и bool, p, мы ее значит чем то иницизируем, пятеркой и значением true. У нас все компелируется, теперь идем в документацию пары, вот она у меня здесь открыта, и вот значит teplate, T1, T2, struct pair. Открываем конструктор пары и смотрим вот сюда в блок 2. Мы видим, что у пары есть конструктор, который принимает параметры типа Т1 и типа Т2. У нас ест шаблон класса pair, у него есть конструктор, который принимает параметры его шаблонных. аргументов, поэтому компилятор может воспользоваться вот этим конструктором и вывести тип. Поэтому в коде мы можем просто написать pair p(5, true ) и наш код скомпилируется, потому что компилятор по вот этой вот пятерке и вот этому значению true поймёт, что нам нужно из шаблона pair создать класс pair от int и bool. Вот таким образом это работает. Давайте теперь вернемся к нашему шаблону iterator range, на примере которого мы собственно рассматриваем шаблоны классов и модифицируем его таким образом, чтобы для него работал вот этот вот вывод шаблонных аргументов. Сделаем мы вот что, мы его из структуры превратим в класс. Поля firs и second сделаем приватными. А здесь делаем его конструктор iterator range, который будет принимать собственно iterator f и iterator l, first и last и будет инициализировать. Поле first параметром f, в поле last параметрам l, у нас компилируется, и тогда, вот здесь у нас ниже уже были примеры, которые мы использовали для порождающих функций, мы можем написать iterator range second half и проинициализировать его вот этими параметрами, v begin + v size по полам и v end, компилируем, компилируется и работает все так же выводит вторую половину вектора. То есть мы смогли воспользоваться вот этим способом вывода типов. Компилятор видит, что мы создаем объект класса с помощью двух аргументов типа вектор int iterator, он понимает, благодаря этому конструктору, что в конструктор передали вектор int iterator, значит мы должны инстанцировать шаблон iterator range с помощью типа вектор int iterator и таким образом он нам создает объект класса iterator range от vector int iterator. Собственно давайте рассмотрим преимущества способов введения типов через конструктор. Эти преимущества противоположны недостаткам порождающих функций. Нам не всегда нужно писать дополнительный код, то есть если в классе есть уже конструктор, принимающий все шаблонные типы, то у нас все работает из коробки, нам ничего делать не надо. Сейчас в примере с iterator range нам пришлось наш шаблон немного поправить, добавив в него конструктор, но, например, со стандартными контейнерами, там уже есть все необходимое конструкторы, которые позволяют пользоваться этим способом. Ну и важный момент, когда мы объявляем переменную, вот как здесь переменная full, у неё вместо слова auto, у нее просто указан имя шаблона, iterator range, и читая этот код нам проще понимать какой тип имеет переменная ful, т.е. мы понимаем, что тут не написан тип, с которым этот шаблон будет инстанцирован, но мы например, понимаем какой интерфейс будет у объекта full. Какие недостатки у нас есть при использовании вот таких конструкторов? Ну вот у меня на экране приведен пример, в котором объявлены две переменные, r_i и r_s и из их объявления кажется, что переменные r_i и r_s имеют один и тот же тип, потому что у нас и там и тут написано iterator range r_i, r_s, хотя на самом деле эти переменные имеют разные типы. Первая iterator range этоveсtor int iterator, другая iterator range от vector string iterator. Вот поэтому такой код может просто немного ввести в заблуждение. Давайте подведем итоги. Мы рассмотрели с вами два способа перекладывания вывода типов на компилятор и какие можем сделать выводы. Бывают ситуации, когда явное указание шаблонного типа при его инстанцировании может быть громоздким, оно может усложнять чтение кода. В предыдущем видео мы так явно инстанцировали iterator range от iteratorа на vector int. Поэтому есть два способа перекладывания вывода типов на компилятор - порождающие функции и специальный конструктор принимающий аргументы всех шаблонных типов. По умолчанию, когда вы пишете свой код, то в первую очередь вы должны стараться явно указывать шаблонный тип при инстанцировании шаблона классов, как приведено на экране, то есть мы пишем полностью имя шаблона и в угловых скобках тип, vector<int>, map<string, int>, set vector строк, по умолчанию мы делаем так, потому что такой код проще читать, мы сразу видим полностью целиковий тип переменной, которую мы объявляем. Если вот это вот поведение, этот способ по умолчанию приводит к тому, что нас получается громоздкий код, мы используем второй способ выведения типов, т.е. через конструктор, потому что в нем явно указано имя шаблона, которое мы инстанцируем. Но вот приведем пример, с созданием переменной second half мы написали iterator range и при чтении кода понятно какой шаблон мы используем. И другой пример с парой. И только если у нас получается громоздкий код, но мы по какой-то причине не можем воспользоваться этим способом с конструктором, например, в шаблоне нет подходящих конструкторов, тогда мы используем порождающую функцию. Это такой самый последний третий вариант, потому что нам придется написать какой-то код, да, и это лишний код, лишняя трата времени. Теперь вы знакомы с такими современными способами перекладывание работы на компилятор при использовании шаблонных классов.