SFINAE и ограничения
Выбор перегрузок по типам: enable_if, tag dispatch, идиома обнаружения и то, что исправили концепты
- объяснить, что такое ошибка подстановки и где именно работает 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. Ошибки компиляции нет.
Фраза "не является ошибкой" - это и есть суть: будь это жёсткой ошибкой, добавление шаблонной перегрузки могло бы сломать не связанные с ней вызовы. Вместо этого неудачная подстановка просто сокращает список кандидатов.