Давайте ещё
раз поговорим о том, как связаны между собой шаблон класса и класс.
Итак, в нашем примере из предыдущего видео с функцией Head мы с вами написали
шаблон класса IteratorRange, и это именно шаблон класса,
это не сам класс, это не самостоятельный тип, а просто шаблон, в который надо
сначала подставить какой-то конкретный тип, чтобы из него создать класс.
IteratorRange у нас проитеризован типом итератора, поэтому, чтобы создать из него
какой-то конкретный класс, в него надо подставить какой-то итератор,
например итератор вектора целых чисел.
И вот тогда вот эта вот вещь целиком — IteratorRange от
vector int integrator — тогда это целиком получится
самостоятельный отдельный класс.
Мне кажется, что этот синтаксис, когда мы пишем какое-то слово,
потом в угловых скобках указываем какой-то тип и получаем какой-то класс,
какой-то самостоятельный тип, вот этот синтаксис вам может быть очень,
вернее, даже должен быть очень вам знаком.
Собственно, давайте посмотрим.
Вот у нас есть наши стандартные vector, map и set.
И мы берём и в угловых скобках добавляем к ним какие-то типы.
У нас получается vector int, map из строки vector int и, например, множество строк.
И, собственно, этим примером я хочу просто сказать, что все стандартные контейнеры,
которыми мы с вами пользовались — vector, map, set, para,
deck — все эти контейнеры являются шаблонами классов.
То есть в библиотеке C++ это шаблоны классов,
которые, собственно, при подстановке туда типа,
генерируют вам тип контейнера для конкретного типа элементов.
Так что теперь вы знаете эту вещь,
что стандартные контейнеры являются шаблонами классов.
И это позволит вам проще читать документацию.
Вот у меня открыта документация на map.
И вы теперь можете понимать, что, собственно, здесь написано.
Вы видите, что у нас есть класс map, но это не просто класс,
а это шаблон класса, потому что он начинается с ключевого слова template и
дальше перечислены его параметры.
class Key — это ключ, class T — это значение,
и также у него есть ещё два шаблонных параметра, это компоратор и аллокатор,
мы пока их не рассматривали и, тем более, у них есть значения по умолчанию.
Поэтому если вы их не указываете, то у вас всё равно всё работает.
Кроме того, на что ещё нужно обратить внимание?
Что когда у нас есть какой-то шаблон класса, в данном случае — vector,
и мы к этом шаблону подставляем разные конкретные типы,
то есть мы этот шаблон инстанцируем с разными типами.
Вот как в данном случае мы инстанцировали шаблон vector c
параметрами c типом string, int и double.
В этом случае у нас получаются разные типы,
то есть vector int и вектор string — это совершенно
разные с точки зрения компилятора C++ типы.
Что я имею ввиду?
То есть как это проявляется на практике?
Но вот смотрите.
У нас есть, например, vector int V.
И у нас есть, например, vector double D.
Мы не можем написать V равно D, потому что это два разных типа,
вектор целых числе и вектор дробных чисел.
Вот, собственно, нам об этом и компилятор говорит,
что нет оператора равно для вектора int и для вектора double.
Так что просто вы должны вот эту концептуальную вещь усвоить,
что когда мы из шаблона класса подставляем туда разные типы,
когда мы инстанцируем с разными типами, получаются разные классы.
И ещё одна важная вещь.
Вот у нас есть IteratorRange,
диапазон двух итераторов.
Мы можем захотеть написать функцию, которая считает размер этого диапазона,
сколько в этом диапазоне элементов.
Мы не можем написать её вот так.
Значит, например, size_t
RangeSize IteratorRange
r return r.end минус r.begin.
И давайте ею воспользуемся,
выведем RangeSize от Head v 3.
В данном случае мы ожидаем увидить 3,
но наша программа просто не компилируется.
И не компилируется она с какими-то непонятными сообщениями об ошибке.
В данном случае из этих сообщений трудно что-то понять,
но смотрите: мы объявили функцию и у неё есть параметр r.
У этого параметра должен быть какой-то тип.
Мы указали IteratorRange.
IteratorRange — это не тип, это шаблон типа.
Чтобы из этого шаблона создать конкретный тип, его надо истанцировать.
Поэтому чтобы у нас была функция RangeSize, мы что должны сделать?
Мы должны сделать её шаблонной и проинстанцировать
вот этот шаблон IteratorRange с помощью типа T.
Вот эта запись — IteratorRange от T — это уже конкретный тип.
И поэтому в таком виде наша программа компилируется, работает и выводит 3.
То есть вот эта важная вещь,
которую вы тоже должны усвоить, что шаблон класса — это шаблон,
а класс — это та вещь, которая из шаблона получается.
Нельзя использовать просто шаблон без инстанцирования в том месте,
где язык ожидает увидить тип.