WikiSort.ru - Программирование

ПОИСК ПО САЙТУ | о проекте

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).

  1. Сначала компилятор отбрасывает функции, которые не подходят по количеству параметров — 4 и 5.
  2. Затем отбрасываются шаблонные подстановки, где не удалось рассчитать типы входных параметров и возврата — таковых нет.
  3. Потом отбрасывается функция 1 — для неё нет подходящего преобразования типов.
  4. И уж из 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[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 .




    Текст в блоке "Читать" взят с сайта "Википедия" и доступен по лицензии Creative Commons Attribution-ShareAlike; в отдельных случаях могут действовать дополнительные условия.

    Другой контент может иметь иную лицензию. Перед использованием материалов сайта WikiSort.ru внимательно изучите правила лицензирования конкретных элементов наполнения сайта.

    2019-2024
    WikiSort.ru - проект по пересортировке и дополнению контента Википедии