Перейти к содержанию

Начало урока

Концепты (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 должен быть целочисленным") погребён в шаблонной обвязке, сообщения об ошибках всё так же ужасны, а ограничение нельзя переиспользовать или назвать. Концепты заменяют всё это читаемым, именованным контрактом.

Продолжите этот урок на YourAIZone

В полном уроке — объяснение целиком, разобранные примеры, частые ошибки, тесты с мгновенной проверкой и ИИ-репетитор. Начните сразу как гость или создайте аккаунт, чтобы сохранять прогресс.

В полном уроке

  • Объяснение целиком
  • Разобранные примеры
  • Частые ошибки и как их избежать
  • Тестовые вопросы: 15
  • ИИ-репетитор, который отвечает на вопросы по теме
  • Отслеживание прогресса для ученика, родителей и учителя