Концепты (C++20)
Читаемое ограничение шаблонов - именованные, проверяемые требования к параметрам шаблона
- объяснить, почему неограниченные шаблоны и старые приёмы
enable_if/SFINAE дают ужасные ошибки, - применять концепты стандартной библиотеки (
std::integral,std::convertible_to,std::sortable, ...), - прикреплять ограничения четырьмя разными способами, включая сокращённую форму с
auto, - писать свой концепт с помощью
requires-выражения (простые, составные, типовые и вложенные требования), - использовать поглощение (subsumption) ограничений для выбора самой специализированной перегрузки.
1. Проблема: неограниченные шаблоны и SFINAE
Вы уже умеете писать шаблон функции. Беда в том, что голый template<typename T> не обещает ничего. Требования к типу живут только внутри тела, поэтому неверный тип компилируется до того момента, когда какая-то внутренняя операция не сработает:
template <typename T>
T twice(T x) { return x + x; } // молча предполагает, что у T есть operator+
twice(std::string{"hi"}); // компилируется: "hihi"
twice(std::vector<int>{1,2}); // ОШИБКА - но глубоко внутри <vector>, страницы шума
До C++20 решением был SFINAE ("Substitution Failure Is Not An Error") с std::enable_if: вы протаскивали ограничение в сигнатуру, чтобы плохой тип заставлял перегрузку исчезнуть. Это работало, но было доступно только на запись:
template <typename T,
typename = std::enable_if_t<std::is_integral_v<T>>>
T twice(T x) { return x + x; } // "только если T целочисленный" - но кто это прочитает?
Замысел ("T должен быть целочисленным") погребён в шаблонной обвязке, сообщения об ошибках всё так же ужасны, а ограничение нельзя переиспользовать или назвать. Концепты заменяют всё это читаемым, именованным контрактом.