Неудачная замена не является ошибкой
Ошибка замены не является ошибкой ( 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 можно использовать, чтобы определить, содержит ли тип определенное определение типа:
#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
. Многоточие используется не только потому, что оно принимает любой аргумент, но и потому, что его ранг преобразования самый низкий, поэтому вызов первой функции будет предпочтительнее, если это возможно; это устраняет двусмысленность.
Упрощение С++ 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;
}
Благодаря стандартизации идиомы обнаружения в предложении Library 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.
- ^ Включить усиление, если