Отказ замены не является ошибкой
Отказ замены не является ошибкой ( sfinae ) является принципом в C ++, где неверная замена параметров шаблона сама по себе не является ошибкой. Дэвид Вандевоорд впервые представил аббревиатуру Sfinae для описания связанных методов программирования. [ 1 ]
В частности, при создании кандидата, установленного для разрешения перегрузки , некоторые (или все) кандидаты этого набора могут быть результатом экземплярных шаблонов с (потенциально выведенными) аргументами шаблонов, заменяемых соответствующими параметрами шаблона. Если возникает ошибка во время замены набора аргументов для любого данного шаблона, компилятор удаляет потенциальную перегрузку из набора кандидатов вместо того, чтобы останавливаться с ошибкой компиляции, при условии, что стандарт C ++ позволяет отбросить ошибку замены, как упомянуто. [ 2 ] Если один или несколько кандидатов остаются, и разрешение перегрузки преуспевает, вызов хорошо сформирован.
Пример
[ редактировать ]Следующий пример иллюстрирует основной экземпляр Sfinae:
struct Test {
typedef int foo;
};
template <typename T>
void f(typename T::foo) {} // Definition #1
template <typename T>
void f(T) {} // Definition #2
int main() {
f<Test>(10); // Call #1.
f<int>(10); // Call #2. Without error (even though there is no int::foo)
// thanks to SFINAE.
return 0;
}
Здесь попытка использовать не класс типа в квалифицированном имени ( T::foo
) приводит к вычтению сбоя для f<int>
потому что int
не имеет вложенного типа названного foo
, но программа хорошо сформирована, потому что в наборе функций кандидатов остается действительная функция.
Несмотря на то, что Sfinae был первоначально введен во избежание создания плохо сформированных программ, когда были видны неродственные декларации шаблонов (например, через включение файла заголовка), многие разработчики позже обнаружили, что поведение полезно для самоанализа времени компиляции. В частности, он позволяет шаблону определять определенные свойства своих шаблонных аргументов во время экземпляра.
Например, Sfinae можно использовать для определения того, содержит ли тип определенный Typedef:
#include <iostream>
template <typename T>
struct has_typedef_foobar {
// Types "yes" and "no" are guaranteed to have different sizes,
// specifically sizeof(yes) == 1 and sizeof(no) == 2.
typedef char yes[1];
typedef char no[2];
template <typename C>
static yes& test(typename C::foobar*);
template <typename>
static no& test(...);
// If the "sizeof" of the result of calling test<T>(nullptr) is equal to
// sizeof(yes), the first overload worked and T has a nested type named
// 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; // Prints false
std::cout << has_typedef_foobar<foo>::value << std::endl; // Prints true
return 0;
}
Когда T
имеет вложенный тип foobar
определено, экземпляр первого test
Работы и константа нулевого указателя успешно прошли. (И полученный тип выражения yes
.) Если он не работает, единственная доступная функция - вторая test
и полученный тип выражения no
Полем Ellipsis используется не только потому, что он примет любой аргумент, но и потому, что его ранг преобразования является самым низким, поэтому вызов первой функции будет предпочтительным, если это возможно; Это устраняет двусмысленность.
C ++ 11 Упрощение
[ редактировать ]В C ++ 11 приведенный выше код может быть упрощен до:
#include <iostream>
#include <type_traits>
template <typename... Ts>
using void_t = void;
template <typename T, typename = void>
struct has_typedef_foobar : std::false_type {};
template <typename T>
struct has_typedef_foobar<T, void_t<typename 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;
}
При стандартизации идиомы обнаружения в библиотеке Fundamental V2 (N4562) приведенный выше код может быть переписан следующим образом:
#include <iostream>
#include <type_traits>
template <typename T>
using 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 ] и другими способами.
Ссылки
[ редактировать ]- ^ Видес, Дэвид; Николай М. Джосуттис (2002). Шаблоны C ++: Полное руководство . Аддисон-Уэсли Профессионал. ISBN 0-201-73484-2 .
- ^ Международная организация по стандартизации. «ISO/IEC 14882: 2003, языки программирования - C ++», § 14.8.2.
- ^ Увеличить включение, если