SFINAE (англ. substitution failure is not an error, «неудавшаяся подстановка — не ошибка») — механизм языка C++, связанный с шаблонами и перегрузкой функций.
Правило SFINAE гласит: Если не получается рассчитать окончательные типы аргументов (провести подстановку шаблонных параметров) перегруженной шаблонной функции, компилятор не выбрасывает ошибку, а ищет другую подходящую перегрузку. Ошибка будет в трёх случаях:
Правило существовало ещё в C++98, и было придумано, чтобы программа не выдавала ошибок, если где-то в заголовочных файлах оказался одноимённый шаблон, далёкий от контекста. Но впоследствии оно оказалось удобно для рефлексии при компиляции — в зависимости от свойств типа компиляция идёт по тому или другому пути. Саму аббревиатуру SFINAE придумал Дэвид Вандервурд, автор книги «Шаблоны C++» (2002).
В стандарте C++11 правило SFINAE было несколько уточнено, концептуально не меняясь. Также в C++11 добавили несложный шаблон enable_if
, действующий на правиле SFINAE и позволяющий инстанцировать шаблон при определённых условиях.
В C++17 добавили конструкцию if constexpr()
, несколько снизившую надобность в SFINAE.
В C++20 обещают конструкцию explicit (true)
, и правило 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(...);
Компилятор собирает эти функции в список и находит лучшую по определённым правилам — производит разрешение перегрузки (англ. overload resolution).
Шаг 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
в шаблон, и отбрасываются те шаблоны, где подстановка не привела к успеху. Потому неудавшаяся подстановка — не ошибка.
Этот пример компилируется даже на 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[1]; // typedef for an array of size one.
typedef char No[2]; // 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;
}
Принцип действия: в строке sizeof(func<Derived>(0))
происходит разрешение перегрузки, и есть два варианта.
Первый шаблон func
, возвращающий тип No
, инстанцируется, если у объекта Check
единственный член find
— тот, который взят из предка Fallback
. Если не удалось подставить параметры в первый шаблон, инстанцируется второй, возвращающий тип Yes
.
Данная страница на сайте WikiSort.ru содержит текст со страницы сайта "Википедия".
Если Вы хотите её отредактировать, то можете сделать это на странице редактирования в Википедии.
Если сделанные Вами правки не будут кем-нибудь удалены, то через несколько дней они появятся на сайте WikiSort.ru .