SFINAE
Warunkowo włączaj funkcje
SFINAE to bezpłatna lekcja C++ Academy na CoddyKit. To lekcja 3 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej C++ Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs C++ Academy zawiera 4 lekcji w sumie.
Co oznacza SFINAE
SFINAE to skrót od „Substitution Failure Is Not An Error”. Gdy podczas podstawiania argumentów szablonu przez kompilator wynik jest niepoprawny, dany kandydat zostaje po prostu usunięty, zamiast powodować poważny błąd kompilacji.
- Umożliwia warunkowe przeciążanie.
- Stanowi podstawę wyboru w czasie kompilacji.
#include <iostream>
#include <type_traits>
template <typename T>
typename std::enable_if<std::is_integral<T>::value, bool>::type
isOdd(T n) { return n % 2 != 0; }
int main() {
std::cout << std::boolalpha << isOdd(3) << '\n';
return 0;
}Podstawienie w praktyce
Jeśli podstawienie typu sprawia, że sygnatura funkcji staje się niepoprawna, kompilator po cichu pomija tę funkcję i analizuje pozostałych kandydatów.
#include <iostream>
template <typename T>
auto sizeOfValue(T t) -> decltype(t.size()) { return t.size(); }
int main() {
std::string s = "hello";
std::cout << sizeOfValue(s) << '\n';
return 0;
}Dwa przeciążenia, jedno wygrywa
Należy zapewnić dwa przeciążenia, z których dla danego typu dokładnie jedno pomyślnie przejdzie podstawienie. SFINAE odrzuci niepoprawne przeciążenie.
#include <iostream>
#include <type_traits>
template <typename T>
typename std::enable_if<std::is_integral<T>::value, const char*>::type
name() { return "integral"; }
template <typename T>
typename std::enable_if<std::is_floating_point<T>::value, const char*>::type
name() { return "floating"; }
int main() {
std::cout << name<int>() << ' ' << name<double>() << '\n';
return 0;
}decltype i SFINAE wyrażeń
Użycie decltype dla wyrażenia w typie zwracanym sprawia, że funkcja istnieje tylko wtedy, gdy to wyrażenie jest poprawne dla danego typu.
#include <iostream>
template <typename T>
auto tryDouble(T x) -> decltype(x + x) { return x + x; }
int main() {
std::cout << tryDouble(21) << '\n';
std::cout << tryDouble(2.5) << '\n';
return 0;
}Wykrywanie składowej
Klasyczna sztuczka z SFINAE pozwala wykryć, czy typ ma określoną składową, sprawdzając, czy kompiluje się wyrażenie, które jej używa.
#include <iostream>
#include <type_traits>
#include <vector>
template <typename T>
auto hasSize(int) -> decltype(std::declval<T>().size(), std::true_type{});
template <typename T>
std::false_type hasSize(...);
int main() {
std::cout << std::boolalpha;
std::cout << decltype(hasSize<std::vector<int>>(0))::value << '\n';
std::cout << decltype(hasSize<int>(0))::value << '\n';
return 0;
}Awaryjne przeciążenie ...
Przeciążenie przyjmujące ... (varargs) ma najniższy priorytet. Służy jako rozwiązanie awaryjne, gdy przeciążenie ograniczone przez SFINAE nie może zostać użyte.
#include <iostream>
template <typename T>
auto pick(int) -> decltype(T{}.value, void()) { std::cout << "has value\n"; }
template <typename T>
void pick(...) { std::cout << "no value\n"; }
struct WithValue { int value = 0; };
int main() {
pick<WithValue>(0);
pick<int>(0);
return 0;
}void_t
Wprowadzony w C++17 typ std::void_t upraszcza wykrywanie: mapuje wszystkie poprawnie sformułowane typy na void, dzięki czemu specjalizacja jest wybierana tylko wtedy, gdy sprawdzane wyrażenie jest poprawne.
#include <iostream>
#include <type_traits>
#include <vector>
template <typename T, typename = void>
struct HasSize : std::false_type {};
template <typename T>
struct HasSize<T, std::void_t<decltype(std::declval<T>().size())>> : std::true_type {};
int main() {
std::cout << std::boolalpha;
std::cout << HasSize<std::vector<int>>::value << '\n';
std::cout << HasSize<int>::value << '\n';
return 0;
}Dlaczego nie zgłosić po prostu błędu
Bez SFINAE niepoprawne podstawienie powodowałoby poważny błąd kompilacji, którego nie można byłoby obsłużyć. SFINAE zamienia je w ciche oznaczenie „nie jest kandydatem”.
#include <iostream>
#include <type_traits>
template <typename T>
typename std::enable_if<std::is_pointer<T>::value, bool>::type
isNull(T p) { return p == nullptr; }
int main() {
int x = 0;
std::cout << std::boolalpha << isNull(&x) << '\n';
return 0;
}Alternatywa z tag dispatch
SFINAE może być trudne do odczytania. Tag dispatch wybiera implementację za pomocą niewielkich typów znacznikowych i rozstrzygania przeciążeń.
#include <iostream>
#include <type_traits>
template <typename T>
void impl(T v, std::true_type) { std::cout << "integral: " << v << '\n'; }
template <typename T>
void impl(T v, std::false_type) { std::cout << "other: " << v << '\n'; }
template <typename T>
void handle(T v) { impl(v, std::is_integral<T>{}); }
int main() {
handle(5);
handle(2.5);
return 0;
}SFINAE i constexpr if
W C++17 konstrukcja if constexpr często zastępuje SFINAE przy rozgałęzianiu wewnątrz jednej funkcji, ponieważ nieużywane gałęzie są odrzucane w czasie kompilacji.
#include <iostream>
#include <type_traits>
template <typename T>
void describe(T v) {
if constexpr (std::is_integral<T>::value) {
std::cout << "integral: " << v << '\n';
} else {
std::cout << "other: " << v << '\n';
}
}
int main() {
describe(7);
describe(3.14);
return 0;
}Połączenie wszystkiego
SFINAE jest szczególnie przydatne, gdy trzeba wybierać między osobnymi szablonami funkcji na podstawie właściwości typów na poziomie przeciążeń.
#include <iostream>
#include <type_traits>
template <typename T>
typename std::enable_if<std::is_signed<T>::value, T>::type
absValue(T v) { return v < 0 ? -v : v; }
int main() {
std::cout << absValue(-9) << '\n';
return 0;
}Szybkie sprawdzenie
Proszę sprawdzić swoje rozumienie SFINAE.
Podsumowanie
Poznali Państwo SFINAE:
- niepoprawne podstawienie usuwa kandydata zamiast powodować błąd
- mechanizm jest używany z
enable_if,decltypeivoid_tdo warunkowego włączania funkcji - przeciążenie
...pełni funkcję rozwiązania awaryjnego - nowoczesne alternatywy to tag dispatch i
if constexpr
Następnie szczegółowo poznają Państwo klasyczne wzorce użycia enable_if.
Często zadawane pytania
Czy lekcja „SFINAE” jest bezpłatna?
Tak — pełny tekst „SFINAE” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu C++ Academy, przejdź na CoddyKit PRO. Kurs C++ Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „SFINAE”?
Warunkowo włączaj funkcje Ćwiczysz C++ Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć C++ Academy?
Nie wymagamy żadnego doświadczenia. C++ Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 4.
Ile czasu zajmuje lekcja „SFINAE”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji C++ Academy?
Tak. Każda lekcja C++ Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.