SFINAE (Substitution Failure Is Not An Error) - это принцип в C++, согласно которому ошибка подстановки (замены) шаблонного параметра не приводит к ошибке компиляции, а просто исключает перегруженную функцию из списка кандидатов.
При разрешении перегрузки функций компилятор:
Пример базового механизма:
template<typename T>
void foo(T t, typename T::inner_type* = nullptr) {
// Реализация для типов, имеющих inner_type
}
template<typename T>
void foo(T t) {
// Общая реализация
}
struct A { using inner_type = int; };
struct B {};
foo(A{}); // Выбирается первая перегрузка
foo(B{}); // Выбирается вторая, так как B::inner_type не существует
template<typename T>
class has_serialize {
template<typename U>
static auto test(int) -> decltype(std::declval<U>().serialize(), std::true_type{});
template<typename>
static std::false_type test(...);
public:
static constexpr bool value = decltype(test<T>(0))::value;
};
struct X { void serialize() {} };
struct Y {};
static_assert(has_serialize<X>::value, "X should have serialize");
static_assert(!has_serialize<Y>::value, "Y shouldn't have serialize");
template<typename T>
auto serialize(T& obj) -> decltype(obj.serialize(), void()) {
obj.serialize();
}
template<typename T>
void serialize(T& obj) {
// Общая реализация для типов без serialize()
}
template<typename T>
struct is_pointer {
template<typename U>
static std::true_type test(U*);
static std::false_type test(...);
static constexpr bool value = decltype(test(std::declval<T>()))::value;
};
template<typename T>
void process(T obj) {
if constexpr (has_serialize<T>::value) {
obj.serialize();
} else {
// Альтернативная обработка
}
}
template<typename T>
concept Serializable = requires(T t) {
{ t.serialize() } -> std::same_as<void>;
};
template<Serializable T>
void serialize(T& obj) {
obj.serialize();
}
template<typename T>
typename std::enable_if<std::is_integral<T>::value, T>::type
foo(T t) {
// Только для целочисленных типов
}
template<typename T>
typename std::enable_if<!std::is_integral<T>::value, T>::type
foo(T t) {
// Для остальных типов
}
template<typename T, typename = std::void_t<>>
struct has_member : std::false_type {};
template<typename T>
struct has_member<T, std::void_t<decltype(std::declval<T>().member)>> : std::true_type {};
SFINAE - это мощный механизм в C++, который позволяет:
Основные случаи использования:
С появлением концептов в C++20 многие сценарии использования SFINAE стали проще, но понимание этого механизма остаётся важным для работы с legacy-кодом и глубокого понимания шаблонов C++.