SFINAE (, «неудавшаяся подстановка — не ошибка») — правило языка C++, связанное с шаблонами и перегрузкой функций. Широко применяется «не по назначению» — для рефлексии при компиляции: в зависимости от свойств типа компиляция идёт по тому или другому пути.

Правило SFINAE гласит: Если не получается рассчитать окончательные типы/значения шаблонных параметров функции, компилятор не выбрасывает ошибку, а ищет другую подходящую перегрузку. Ошибка будет в трёх случаях: Не нашлось ни одной подходящей перегрузки. Нашлось несколько таких перегрузок, и компилятор не может решить, какую взять. Перегрузка нашлась, она оказалась шаблонной, и при инстанцировании шаблона случилась ошибка.

История Правило существовало ещё в C++98, и было придумано, чтобы программа не выдавала ошибок, если где-то в заголовочных файлах оказался одноимённый шаблон, далёкий от контекста. Но впоследствии оно оказалось удобно для рефлексии при компиляции. Саму аббревиатуру SFINAE придумал Дэвид Вандервурд, автор книги «Шаблоны C++» (2002).

В Boost добавили несложный шаблон , действующий на правиле SFINAE и позволяющий инстанцировать шаблон при определённых условиях.

В стандарте C++11 правило SFINAE было несколько уточнено, концептуально не меняясь. Туда же вошёл и шаблон (вообще у Boost позаимствованы , , и многое другое).

В C++17 добавили конструкцию , несколько снизившую надобность в SFINAE.

В C++20 появилась конструкция . С одной стороны, константа в скобках — тоже часть подстановки, и если её не получится рассчитать, это будет неудавшаяся подстановка. С другой — она также снижает надобность в SFINAE.

Изначальное назначение Предположим, надо вызвать функцию

f(1, 2);

Есть такие версии этой функции: (1) void f(int, std::vector<int>); (2) void f(int, int); (3) void f(double, double); (4) void f(int, int, char, std::string, std::vector<int>); (5) void f(std::string); (6) void f(...);

Компилятор собирает эти функции в список и находит лучшую по определённым правилам — производит разрешение перегрузки (). Сначала компилятор отбрасывает функции, которые не подходят по количеству параметров — (4) и (5). Затем отбрасываются шаблонные подстановки, где не удалось рассчитать типы входных параметров и возврата — таковых нет. Потом отбрасывается функция (1) — для неё нет подходящего преобразования типов. Из оставшихся (2), (3) и (6) по довольно сложным правилам компилятор выбирает (2) — оба типа точно совпадают. Если бы такого «абсолютного победителя» не было, компилятор выдал бы ошибку, указав, между какими вариантами он колеблется.

Шаг 2, связанный с шаблонными функциями, пока не задействован. Добавим к нашему списку ещё две функции. (7) template<typename T> void f(T, T); (8) template<typename T> void f(T, typename T::iterator);

Функция 7 будет отброшена на четвёртом шаге, потому что нешаблонная функция всегда «сильнее» шаблонной.

Шаблон 8 далёк от нашей задачи, так как рассчитан на некий класс, имеющий внутри тип iterator. Второй шаг и есть SFINAE: компилятор говорит, что T = int, пробует подставить int в шаблон, и отбрасываются те шаблоны, где подстановка не привела к успеху. Поэтому неудавшаяся подстановка — не ошибка.

Пример рефлексии при компиляции через SFINAE Этот пример компилируется даже на C++03. #include <iostream> #include <vector> #include <set>

template<typename T> class DetectFind { struct Fallback { int find; }; // add member name "find" struct Derived : T, Fallback { };

template<typename U, U> struct Check;

typedef char Yes; // typedef for an array of size one. typedef char No; // typedef for an array of size two.

template<typename U> static No& func(Check<int Fallback::*, &U::find> *);

template<typename U> static Yes& func(...);

public: typedef DetectFind type; enum { value = sizeof(func<Derived>(0)) == sizeof(Yes) };

int main() { std::cout << DetectFind<std::vector<int> >::value << ' ' << DetectFind<std::set<int> >::value << std::endl; return 0; }

Принцип действия: в строке происходит разрешение перегрузки, и конкретный тип сильнее, чем переменные аргументы . Из-за того, что под , нет нужды инстанцировать шаблонные функции, достаточно подставить типы — потому у функций только заголовки без тел. Вторая функция, возвращающая тип , подставится всегда, а что же с первой?

Она подставится, если шаблонный тип будет существовать (поскольку под указателем, точный тип не важен, главное — существование). Первый параметр шаблона — тип, второй — константа этого типа. В качестве типа берётся указатель на -поле объекта (по факту — смещение от начала объекта до поля), в качестве константы — указатель на поле . Константа будет определена и иметь нужный тип, если единственное поле взято у объекта  — то есть отсутствует другой , позаимствованный у .

Примечания

Ссылки

На русском

Оригинал:

Категория:Шаблоны C++