SFINAE

SFINAE ( Eșecul de substituție în engleză  nu este o eroare , „substituția eșuată nu este o eroare”) este o regulă de limbaj C ++ asociată cu șabloanele și supraîncărcarea funcțiilor . Este utilizat pe scară largă „în alte scopuri” - pentru reflecție în timpul compilării : în funcție de proprietățile tipului, compilarea merge într-un fel sau altul.

Regula SFINAE spune: Dacă tipurile/valorile finale ale parametrilor șablonului unei funcții nu pot fi calculate, compilatorul nu aruncă o eroare, ci caută o altă supraîncărcare adecvată. Eroarea va fi în trei cazuri:

Istorie

Regula exista în C++98 și a fost inventată astfel încât programul să nu genereze erori dacă undeva în fișierele antet ar exista un șablon cu același nume, departe de context. Dar mai târziu s-a dovedit a fi convenabil pentru reflecție în timpul compilării. Acronimul SFINAE a fost inventat de David Vandervoord, autorul cărții C++ Patterns (2002).

La Boost a fost adăugat un șablon simplu care funcționează pe regula SFINAE și vă permite să instanțiați un șablon în anumite condiții. enable_if

În standardul C++11 , regula SFINAE a fost oarecum rafinată fără a schimba conceptul. A fost introdus și șablonul acolo (în general , , , și multe altele au fost împrumutate de la Boost ).enable_ifchronorandomfilesystem

În C++17 , a fost adăugat un construct care a redus ușor nevoia de SFINAE. if constexpr()

C ++20 a introdus . Pe de o parte, constanta dintre paranteze este, de asemenea, parte a substituției, iar dacă nu reușește să calculeze, va fi o substituție eșuată. Pe de altă parte, reduce și nevoia de SFINAE. De asemenea, a redus necesitatea conceptului SFINAE . explicit (true)

Numire originală

Să presupunem că trebuie să apelăm o funcție

f ( 1 , 2 );

Există versiuni ale acestei funcții:

( 1 ) void f ( int , std :: vector < int > ); ( 2 ) void f ( int , int ); ( 3 ) void f ( dublu , dublu ); ( 4 ) void f ( int , int , char , std :: string , std :: vector < int > ); ( 5 ) void f ( std :: șir ); ( 6 ) nul f (...);

Compilatorul colectează aceste funcții într-o listă și o găsește pe cea mai bună conform anumitor reguli - produce rezoluție de supraîncărcare . 

  1. În primul rând, compilatorul renunță la funcțiile care nu se potrivesc cu numărul de parametri - 4 și 5.
  2. Apoi substituțiile de șablon sunt eliminate, unde nu a fost posibil să se calculeze tipurile de parametri de intrare și să se întoarcă - nu există.
  3. Apoi funcția 1 este eliminată - nu există o conversie de tip adecvată pentru aceasta.
  4. Și de la 2, 3 și 6, conform unor reguli destul de complicate, compilatorul alege 2 - ambele tipuri sunt exact aceleași. Dacă nu ar exista un astfel de câștigător absolut, compilatorul ar arunca o eroare care indică opțiunile între care fluctuează.

Pasul 2, legat de funcțiile șablonului, nu a fost încă activat. Să adăugăm încă două funcții la lista noastră.

( 7 ) template < typename T > gol f ( T , T ); ( 8 ) template < typename T > void f ( T , typename T :: iterator );

Funcția 7 va fi eliminată în al patrulea pas, deoarece o funcție care nu este șablon este întotdeauna „mai puternică” decât una șablon.

Șablonul 8 este departe de sarcina noastră, deoarece este conceput pentru o anumită clasă care are tipul iterator. Al doilea pas este SFINAE : compilatorul spune că T = int, încearcă să înlocuiască intîn șablon, iar acele șabloane în care înlocuirea nu a dus la succes sunt eliminate. Prin urmare, o înlocuire eșuată nu este o eroare .

Un exemplu de reflecție la compilarea cu SFINAE

Acest exemplu se compilează chiar și în C++03 .

#include <iostream> #include <vector> #include <set> template < typenameT > _ clasa DetectFind { struct Fallback { int găsi ; }; // adaugă numele de membru „find” struct Derivat : T , Fallback { }; template < typename U , U > struct Check ; typedef char Da [ 1 ]; // typedef pentru o matrice de dimensiunea unu. typedef char Nr [ 2 ]; // typedef pentru o matrice de dimensiunea doi. șablon < nume tip U > static No & func ( Verificare < int Fallback ::* , & U :: find > * ); șablon < nume tip U > static Da & func (...); public : typedef DetectFind tip ; enum { value = sizeof ( func < Derived > ( 0 )) == sizeof ( Da ) }; }; int main () { std :: cout << DetectFind < std :: vector < int >> :: value << ' ' << DetectFind < std :: set < int > >:: value << std :: endl ; returnează 0 ; }

Cum funcționează: rezoluția supraîncărcării are loc într-un șir , iar tipul concret este mai puternic decât argumentele variabile . Datorită faptului că sub , nu este nevoie să instanțiați funcții șablon, este suficient să înlocuiți tipuri - prin urmare, funcțiile au doar anteturi fără corpuri. A doua funcție, care returnează tipul , va fi întotdeauna înlocuită, dar cum rămâne cu prima? sizeof(func<Derived>(0))Check<int Fallback::*, &U::find> *...funcsizeofYes

Va fi înlocuit dacă tipul șablonului va exista (deoarece sub indicator, tipul exact nu este important, principalul lucru este existența). Primul parametru șablon este un tip, al doilea este o constantă de acel tip. Pointerul către câmpul obiectului este luat ca tip (de fapt, decalajul de la începutul obiectului către câmp), ca constantă, pointerul către câmp . Constanta va fi definită și de tipul corect dacă singurul câmp este luat din obiect  - adică nu există niciun alt împrumutat de la . CheckCheckintFallbackfindDerived::findFallbackfindT

Note

Link -uri

  • SFINAE  (engleză) . cppreference.com. Preluat la 9 ianuarie 2020. Arhivat din original pe 6 mai 2021.
In rusa