-
Notifications
You must be signed in to change notification settings - Fork 0
CPP 15. Ограничения накладываемые на шаблоны. Требования к шаблонам (requires). Концепты. Типы ограничений. Варианты определения шаблонов функций и классов с концептами.
Если есть функции, которые выполняют одни и те же действия, но для разных типов данных, то их можно шаблонизировать.
В С надо либо плодить однотипные функции, что плохо по определению, либо использовать макросы с параметрами, что опасно, так как подстановка идет на этапе компиляции, а проверка - на этапе выполнения.
В С++ эта проблема решается шаблонами.
Шаблоны решают проблему дублирования кода
Шаблон может быть для функции, класса, метода класса, своего типа. Во время компиляции будет подстановка параметров шаблона с проверкой шаблона.
Шаблон не является ни классом, ни функцией. Функция и класс генерируется на основе шаблона во время использования.
Компилятор встречает вызов шаблона функции (создание класса) - смотрит, может ли он быть использован и если может, то по шаблону создается функция(класс).
Начиная со стандарта С++20 в язык был добавлен оператор requires, который позволяет установить для параметров шаблонов ограничения.
template <параметры> requires ограничения
содержимое шаблона;
Ограничения представляют условные выражения, которые возвращают значение типа bool - если параметр типа удовлетворяет условию, то возвращается true. Каждое ограничение предписывает одно или несколько требований для одного или нескольких параметров шаблона.
Например, мы хотим определить функцию, которая может складывать числа:
#include <iostream>
template <typename T> requires std::is_same<T, int>::value || std::is_same<T, double>::value
T sum(T a, T b){ return a + b;}
int main()
{
std::cout << sum(3, 4) << std::endl;
std::cout << sum(12.5, 4.3) << std::endl;
//std::cout << sum(5l, 7l) << std::endl;
}
Здесь определен шаблон функции sum, который принимает значения типа T и возвращает их сумму также в виде значения типа T. Для параметра T после слова requires установлено ограничение
std::is_same<T, int>::value || std::is_same<T, double>::value Для определения ограничения применяется встроенная структура std::is_same из стандартной библиотеки C++. Эта структура в свою очередь типизируется двумя типами. Переменная value структуры возвращает true, если оба типа одинаковы. То есть выражение std::is_same<T, int>::value возвратит true, если T - это int. Аналогично устанавливаем еще одно ограничение к типу - std::is_same<T, double>::value. И с помощью операции || объединяем два ограничения. То есть T может представлять либо int, либо double.
Далее мы можем передавать в функцию sum() значения типов, которые удовлетворяют этим ограничениям:
std::cout << sum(3, 4) << std::endl; // передаем int
std::cout << sum(12.5, 4.3) << std::endl; // передаем double
Значения других же типов мы передать не можем. Так, в примере выше закомментирована строка, где в функцию sum() передаются значения типа long:
//std::cout << sum(5l, 7l) << std::endl; // long работать не будет
Если мы ее раскомментируем, то компилятор не скомпилирует программу и отобразит нам ошибку, которая сообщит, что в функцию sum переданы значения некорректных типов.
template <typename T>
concept size = sizeof(T) <= sizeof(int);
В данном случае определен концепт size. Его смысл в том, что тип, который будет передаваться через параметр T, должен удовлетворять условию sizeof(T) <= sizeof(int). То есть физический размер объектов типа T не должен быть больше размера значений типа int.
Мы можем применять концепты в качестве ограничений для шаблонов
#include <iostream>
template <typename T>
concept size = sizeof(T) <= sizeof(int);
template <typename T> requires size<T> // в качестве ограничения применяется size
T sum(T a, T b){ return a + b;}
int main()
{
std::cout << sum(10, 3) << std::endl; // 13
//std::cout << sum(10.6, 3.7) << std::endl; // ! Ошибка
}
Сокращенный синтаксис
template <size T> // в качестве ограничения применяется size
T sum(T a, T b){ return a + b;}