Если в качестве параметра шаблона выступает тип (``typename``), то все операции в теле шаблона, применяемые к объектам этого типа, должны быть определены. В случае ошибки компиляции выводится не сообщение о невозможности применить шаблонную функцию, а оператор из тела такой функции, в которой используется отсутствующая операция. Для шаблонов класса ошибка часто возникает только при вызове метода, а не при при попытке создать объект.
В С++20 была добавлена возможность проверить тип на необходимые операции при вызове шаблонной функции или создании объекта шаблонного класса. Ошибка компиляции выводится для строки, написанной программистом, а не строки из недр библиотеки.
После
``template <`` *параметры шаблона* ``>``
нужно написать ограничения в форме
``requires`` *предикат*
где *предикат* -- это логическое выражение с использованием логических операций ``&&``, ``||``, ``!`` , проверок типа с помощью концепций, определенных в ``<concepts>``, и константных булевых выражений над другими параметрами шаблона, например,
```c++
#include <concepts>
template <size_t n, typename T>
requires std::integral<T> && (n%4==0)
T fun(T x)
{ return x&n;
}
int main()
{ auto x=fun<100>(15); // Ok, x=4
auto y=fun<2>(100); // Error
auto z=fun<4>(10.0); // Error
}
```
Одним из вариантов константного выражения является
``requires(`` *объявления* ``){`` *проверки* ``}``
где *объявления* -- это объявления объектов через запятую, используемые в проверках, а *проверки* перечисляют операторы с использованием объявленных объектов, которые должны компилироваться, ограничения на тип результата выражений и вложенные ограничения, например,
```c++
#include <concepts>
template <typename T>
requires requires(T& a, size_t k) {
a[k]; // есть операция []
a.size(); // есть метод size()
typename T::value_type; // определен тип value_type в T
{a[k]} -> std::assignable_from<int>; // указанное выражение можно присвоить 0
{a.clear()} noexcept; // это вычисление никогда не приводит к исключению
requires std::destructible<T> && std::constructible_from<T,size_t>; // вложенное ограничения
}
void clear(T& x)
{ for(size_t i=0;i<x.size();++i)
x[i]=0;
}
```
Эту форму можно использовать для условной компиляции:
Так как ограничения часто повторяются, можно задать такому ограничению имя, используя конструкцию
``template <`` *параметры шаблона* ``> concept `` *имя* ``=`` *предикат* ``;``
Указанный после имени концепта тип будет подставлен как первый аргумент в параметры концепта:
```c++
struct A{};
struct B: A{};
struct C {};
template <std::derived_from<A> X>
void fun(X& x);
int main() {
B b;
C c;
fun(b); // Ok
fun(c); // Error
}
```
Концепты | Назначение
--|--
``same_as<`` `X,Y` ``>`` |Типы `X` и `Y` совпадают
``derived_from<`` `X,Y` ``>`` | Тип `X` является производным от типа `Y` открыто
``convertible_to<`` `X,Y` ``>`` | Тип `X` можно преобразовать к `Y` неявно
``common_reference_with<`` `X,Y` ``>`` | Тип `X` имеет общий ссылочный тип с `Y`
``common_with<`` `X,Y` ``>`` | Тип `X` имеет общий тип с `Y`
``integral<`` `X` ``>`` | Тип `X` является целочисленным
``signed_integral<`` `X` ``>`` | Тип `X` является целочисленным со знаком
``signed_integral<`` `X` ``>`` | Тип `X` является целочисленным без знака
``floating_point<`` `X` ``>`` | Тип `X` является вещественным
``assignable_from<`` `X,Y` ``>`` | Значение типа `X` можно присвоить объекту типа `Y`
``swappable<`` `X` ``>`` | Можно обменять состояния двух объектов типа `X` (определен swap)
``swappable_with<`` `X,Y` ``>`` | Можно обменять состояния объектов типа `X` и `Y`
``destructible<`` `X` ``>`` | У типа `X` есть деструктор
``constructible_from<`` `X,Y_1,...,Y_n` ``>`` | У типа `X` есть конструктор с аргументами `Y_1,...,Y_n`
``default_initializable<`` `X` ``>`` | У типа `X` есть конструктор по умолчанию
``move_constructible<`` `X` ``>`` | У типа `X` есть перемещающий конструктор
``copy_constructible<`` `X` ``>`` | У типа `X` есть копирующий и перемещающий конструктор
``movable<`` `X` ``>`` | Объекты типа `X` можно перемещать и обменивать
``copyable<`` `X` ``>`` | Объекты типа `X` можно копировать, перемещать и обменивать
``semiregular<`` `X` ``>`` | Объекты типа `X` являются ``copyable`` и есть конструктор по умолчанию
``regular<`` `X` ``>`` | Объекты типа `X` являются ``semiregular`` и есть операция ``==``
``invocable<`` `X,Y_1,...,Y_n` ``>`` | Тип `X` можно вызвать с аргументами `Y_1,...,Y_n`
``predicate<`` `X,Y_1,...,Y_n` ``>`` | Тип `X` можно вызвать с аргументами `Y_1,...,Y_n` и возвращается результат типа ``bool``
``input_iterator<`` `X` ``>`` | Объект типа `X` является входным итератором
``output_iterator<`` `X` ``>`` | Объект типа `X` является выходным итератором
``random_access_iterator<`` `X` ``>`` | Объект типа `X` является итератором произвольного доступа
...|... больше в ``<iterator>`` и ``<ranges>``