0Pricing
C++ Academy · Lekcja

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, decltype i void_t do 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.

Wszystkie lekcje w tym kursie

  1. Pełna specjalizacja
  2. Częściowa specjalizacja
  3. SFINAE
  4. Wzorce enable_if
← Powrót do C++ Academy