Неудача при замене не является ошибкой

Метод программирования на языке C++

Неудача подстановки не является ошибкой ( SFINAE ) — принцип в C++ , согласно которому недопустимая подстановка параметров шаблона сама по себе не является ошибкой. Дэвид Вандевурд первым ввел аббревиатуру SFINAE для описания связанных методов программирования. [1]

В частности, при создании набора кандидатов для разрешения перегрузки некоторые (или все) кандидаты этого набора могут быть результатом инстанцированных шаблонов с (потенциально выведенными) аргументами шаблона, замещенными на соответствующие параметры шаблона. Если ошибка возникает во время подстановки набора аргументов для любого заданного шаблона, компилятор удаляет потенциальную перегрузку из набора кандидатов вместо остановки с ошибкой компиляции, при условии, что стандарт C++ разрешает отбрасывать такую ​​ошибку подстановки, как упомянуто. [2] Если один или несколько кандидатов остаются и разрешение перегрузки завершается успешно, вызов является правильно сформированным.

Пример

Следующий пример иллюстрирует базовый вариант SFINAE:

struct Test { typedef int foo ; };     шаблон < имя_типа T > void f ( имя_типа T :: foo ) {} // Определение № 1      шаблон < имя_типа T > void f ( T ) {} // Определение №2     int main () { f < Test > ( 10 ); // Вызов № 1. f < int > ( 10 ); // Вызов № 2. Без ошибок (хотя int::foo отсутствует) // спасибо SFINAE. return 0 ; }         

Здесь попытка использовать неклассовый тип в квалифицированном имени ( T::foo) приводит к ошибке вывода, f<int>поскольку intне имеет вложенного типа с именем foo, но программа является правильно сформированной, поскольку в наборе функций-кандидатов остается допустимая функция.

Хотя SFINAE изначально был введен для того, чтобы избежать создания плохо сформированных программ, когда были видны несвязанные объявления шаблонов (например, посредством включения заголовочного файла), многие разработчики позже обнаружили, что поведение полезно для интроспекции во время компиляции. В частности, он позволяет шаблону определять определенные свойства его аргументов шаблона во время инстанцирования.

Например, SFINAE можно использовать для определения, содержит ли тип определенное определение типа:

#include <iostream> template < typename T > struct has_typedef_foobar { // Типы "yes" и "no" гарантированно имеют разные размеры, // в частности sizeof(yes) == 1 и sizeof(no) == 2. typedef char yes [ 1 ]; typedef char no [ 2 ];             шаблон < имя_типа C > статический да & тест ( имя_типа C :: foobar * );       шаблон < typename > статический нет & тест (...);     // Если "sizeof" результата вызова test<T>(nullptr) равен // sizeof(yes), первая перегрузка сработала, и T имеет вложенный тип с именем // foobar. static const bool value = sizeof ( test < T > ( nullptr )) == sizeof ( yes ); };          struct foo { typedef float foobar ; };     int main () { std :: cout << std :: boolalpha ; std :: cout << has_typedef_foobar < int > :: value << std :: endl ; // Выводит false std :: cout << has_typedef_foobar < foo >:: value << std :: endl ; // Выводит true return 0 ; }                   

Если определен Tвложенный тип foobar, то инстанцирование первой testфункции срабатывает, и константа нулевого указателя успешно передается. (И результирующий тип выражения — yes.) Если это не срабатывает, единственной доступной функцией является вторая test, а результирующий тип выражения — no. Многоточие используется не только потому, что оно принимает любой аргумент, но и потому, что его ранг преобразования самый низкий, поэтому вызов первой функции будет предпочтительнее, если это возможно; это устраняет неоднозначность.

Упрощение C++11

В C++11 приведенный выше код можно упростить до:

#include <iostream> #include <type_traits>  шаблон < typename ... Ts > с использованием void_t = void ;     шаблон < имя_типа T , имя_типа = void > структура has_typedef_foobar : std :: false_type {};         шаблон < имя_типа T > структура has_typedef_foobar < T , void_t < имя_типа T :: foobar >> : std :: true_type {};        struct foo { using foobar = float ; };      int main () { std :: cout << std :: boolalpha ; std :: cout << has_typedef_foobar < int >:: value << std :: endl ; std :: cout << has_typedef_foobar < foo >:: value << std :: endl ; return 0 ; }                 

С учетом стандартизации идиомы обнаружения в предложении Library fundamental v2 (n4562) приведенный выше код можно переписать следующим образом:

#include <iostream> #include <type_traits>  шаблон < typename T > с использованием has_typedef_foobar_t = typename T :: foobar ;      struct foo { using foobar = float ; };      int main () { std :: cout << std :: boolalpha ; std :: cout << std :: is_detected < has_typedef_foobar_t , int >:: value << std :: endl ; std :: cout << std :: is_detected < has_typedef_foobar_t , foo >:: value << std :: endl ; return 0 ; }                   

Разработчики Boost использовали SFINAE в boost::enable_if [3] и другими способами.

Ссылки

  1. ^ Вандевурде, Дэвид; Николай М. Джосуттис (2002). Шаблоны C++: Полное руководство . Аддисон-Уэсли Профессионал. ISBN 0-201-73484-2.
  2. ^ Международная организация по стандартизации. «ISO/IEC 14882:2003, Языки программирования – C++», § 14.8.2.
  3. ^ Усиление включить, если
Retrieved from "https://en.wikipedia.org/w/index.php?title=Substitution_failure_is_not_an_error&oldid=1251621101"