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

Начало урока

SFINAE и ограничения

Выбор перегрузок по типам: enable_if, tag dispatch, идиома обнаружения и то, что исправили концепты

Главная идея: SFINAE расшифровывается как Substitution Failure Is Not An Error (ошибка подстановки не является ошибкой компиляции). Когда компилятор пытается инстанцировать шаблон функции и подставленные типы дают некорректную сигнатуру, этот кандидат молча удаляется из набора перегрузок, а не прерывает компиляцию. Именно это тихое удаление позволяет писать шаблоны, которые участвуют только для тех типов, которые действительно поддерживаются, - основа диспетчеризации на этапе компиляции в C++17.
К концу темы вы сможете:
  • объяснить, что такое ошибка подстановки и где именно работает SFINAE (в "непосредственном контексте"),
  • ограничивать шаблоны через std::enable_if - в типе возврата, в параметре шаблона или в параметре-значении по умолчанию,
  • диспетчеризовать по трейтам типов на этапе компиляции с помощью tag dispatch,
  • спрашивать "поддерживает ли тип X?" с помощью идиомы обнаружения (void_t),
  • назвать реальные ограничения SFINAE и понять, почему концепты C++20 заменяют её в большинстве случаев.

1. Разрешение перегрузок и подстановка

Вы уже знаете, что компилятор строит набор перегрузок из функций-кандидатов и затем выбирает наилучшее совпадение. Для шаблонов функций сначала есть дополнительный шаг: компилятор должен подставить выведенные аргументы шаблона в сигнатуру шаблона, чтобы сформировать конкретного кандидата. SFINAE живёт именно на этом шаге подстановки.

Когда подстановка порождает недопустимый тип или выражение в непосредственном контексте сигнатуры - тип возврата, список параметров или список параметров шаблона - это не ошибка. Кандидат просто отбрасывается, а разрешение перегрузок продолжается с оставшимися кандидатами.

// Две перегрузки. Подстановка решает, какая вообще *рассматривается*.
template <typename T>
typename T::value_type first(const T& c) { return *c.begin(); }  // нужен T::value_type

int first(int x) { return x; }                                   // простой запасной вариант

first(std::vector<int>{1,2});  // у vector есть ::value_type -> выбран шаблон
first(42);                     // у int нет ::value_type -> шаблон молча отброшен,
                               //   используется перегрузка для int. Ошибки компиляции нет.

Фраза "не является ошибкой" - это и есть суть: будь это жёсткой ошибкой, добавление шаблонной перегрузки могло бы сломать не связанные с ней вызовы. Вместо этого неудачная подстановка просто сокращает список кандидатов.

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

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

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

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