Uvod u metaprogramiranje na C ++

Prethodna 1 2 3 Stranica 3 Stranica 3 od 3
  • Varijable stanja: parametri predloška
  • Konstrukcije petlje: Kroz rekurziju
  • Izbor putova izvršenja: Korištenjem uvjetnih izraza ili specijalizacija
  • Cijela aritmetika

Ako nema ograničenja za količinu rekurzivnih instanci i broj dopuštenih varijabli stanja, to je dovoljno za izračunavanje bilo čega što se može izračunati. Međutim, možda nije zgodno to učiniti pomoću predložaka. Nadalje, budući da instanciranje predloška zahtijeva znatne resurse kompajlera, opsežna rekurzivna instancija brzo usporava kompajler ili čak iscrpljuje dostupne resurse. C ++ standard preporučuje, ali ne nalaže da se dozvoli minimalno 1.024 razine rekurzivnih instanci, što je dovoljno za većinu (ali sigurno ne svih) zadataka metaprogramiranja predloška.

Stoga bi se u praksi predloženi metaprogrami trebali koristiti štedljivo. Međutim, postoji nekoliko situacija kada su nezamjenjivi kao alat za implementaciju prikladnih predložaka. Konkretno, ponekad se mogu sakriti u unutrašnjosti uobičajenijih predložaka kako bi se istisnule veće performanse iz kritičnih implementacija algoritma.

Rekurzivna instancija naspram rekurzivnih argumenata predloška

Razmotrite sljedeći rekurzivni predložak:

template struct Doublify {}; Problem s predloškom Problem {using LongType = Doublify
   
    ; }; Problem s predloškom Problem {using LongType = double; }; Problemi :: LongType ouch;
   

Korištenje Trouble::LongTypene samo da izaziva rekurzivne pojavljivanje Trouble, Trouble, ..., Trouble, ali također utjelovljuje Doublifypreko sve složenijih tipova. Tablica prikazuje koliko brzo raste.

Rast Trouble::LongType

 
Upišite Alias Osnovni tip
Trouble::LongType double
Trouble::LongType Doublify
Trouble::LongType Doublify

   Doublify>

Trouble::LongType Doublify

     Doublify>,

  

     Doublify>>

Kao što pokazuje tablica, složenost opisa tipa izraza Trouble::LongTypeeksponencijalno raste N. Općenito, takva situacija naglašava kompajler C ++ čak i više nego rekurzivne instancije koje ne uključuju rekurzivne argumente predloška. Ovdje je jedan od problema u tome što kompajler drži prikaz iskrivljenog imena za tip. Ovo iskrivljeno ime na neki način kodira točnu specijalizaciju predloška, ​​a rane implementacije C ++-a koristile su kodiranje koje je približno proporcionalno duljini ID-a predloška. Ovi su kompajleri tada koristili više od 10 000 znakova za Trouble::LongType.

Novije implementacije C ++ uzimaju u obzir činjenicu da su ugnježđeni ID-ovi predložaka prilično česti u modernim programima C ++ i koriste pametne tehnike kompresije da bi znatno smanjili rast kodiranja imena (na primjer, nekoliko stotina znakova za Trouble::LongType). Ovi noviji kompajleri također izbjegavaju generirati iskrivljeno ime ako nijedno nije potrebno, jer se zapravo ne generira kod niske razine za instancu predloška. Ipak, pod jednakim uvjetima, vjerojatno je poželjno organizirati rekurzivnu instanciju na takav način da argumenti predloška ne moraju biti i rekurzivno ugniježđeni.

Vrijednosti nabrajanja nasuprot statičkim konstantama

U ranim danima C ++-a vrijednosti nabrajanja bile su jedini mehanizam koji je stvorio "istinske konstante" (nazvane konstantnim izrazima ) kao imenovani članovi u deklaracijama klasa. Pomoću njih biste mogli, na primjer, definirati Pow3metaprogram za izračunavanje potencijala od 3 na sljedeći način:

meta / pow3enum.hpp // primarni predložak za izračunavanje 3 prema strukturi N-tog predloška Pow3 {enum {vrijednost = 3 * Pow3 :: vrijednost}; }; // puna specijalizacija za završetak predloška rekurzije struct Pow3 {enum {value = 1}; };

Standardizacija C ++ 98 uvela je koncept inicijatora statičkih konstanti u klasi, tako da bi Pow3 metaprogram mogao izgledati kako slijedi:

meta / pow3const.hpp // primarni predložak za izračunavanje 3 prema strukturi N-tog predloška Pow3 {static int const value = 3 * Pow3 :: value; }; // puna specijalizacija za završetak predloška rekurzije struct Pow3 {static int const value = 1; };

Međutim, kod ove verzije postoji nedostatak: Statički konstantni članovi su vrijednosti. Dakle, ako imate izjavu poput

void foo (int const &);

i proslijedite mu rezultat metaprograma:

foo (Pow3 :: vrijednost);

prevodilac mora proći adresu od Pow3::valuete da su snage prevodilac instantiate i dodijeliti definiciju za statičke člana. Kao rezultat, izračunavanje više nije ograničeno na čisti efekt "vremena kompajliranja".

Vrijednosti nabrajanja nisu lvalues ​​(to jest, nemaju adresu). Dakle, kada ih proslijedite referencom, ne koristi se statička memorija. Gotovo je točno kao da ste izračunatu vrijednost predali kao doslovno.

Međutim, C ++ 11 uveo je constexprstatičke članove podataka, a oni nisu ograničeni na integralne tipove. Oni ne rješavaju gore navedeno pitanje s adresom, ali usprkos tom nedostatku, oni su sada uobičajeni način za stvaranje rezultata metaprograma. Oni imaju prednost da imaju ispravan tip (za razliku od umjetnog tipa nabrajanja), a taj se tip može utvrditi kada se statički član deklarira s specifikatorom automatskog tipa. C ++ 17 umetnuta statički članovi podataka, koji rade riješiti pitanje adresu podignuta iznad, a može se koristiti s constexpr.

Povijest metaprogramiranja

Najraniji dokumentirani primjer metaprograma bio je Erwin Unruh, koji je tada zastupao Siemens u odboru za standardizaciju C ++. Primijetio je računsku cjelovitost postupka instanciranja predloška i pokazao svoje stajalište razvijanjem prvog metaprograma. Upotrijebio je Metaware prevodilac i nagovorio ga na izdavanje poruka o pogreškama koje bi sadržavale uzastopne proste brojeve. Evo koda koji je distribuiran na sastanku odbora C ++ 1994. godine (izmijenjen tako da se sada sastavlja na standardnim kompatibilnim kompajlerima):

meta / unruh.cpp // izračunavanje prostih brojeva // (izmijenjeno uz dopuštenje izvornika iz 1994. godine od strane Erwin Unruh) predložak
   
     struct is_prime {enum ((p% i) && is_prime2? p: 0), i-1> :: pri); }; struktura predloška is_prime {enum {pri = 1}; }; struktura predloška is_prime {enum {pri = 1}; }; predložak
    
      struct D {D (void *); }; predložak
     
       struct CondNull {static int const value = i; }; template struct CondNull {static void * vrijednost; }; void * CondNull :: value = 0; predložak
      
        struct Prime_print {
       

// primarni predložak za petlju za ispis prostih brojeva Prime_print a; enum {pri = is_prime :: pri}; void f () {D d = CondNull :: vrijednost;

// 1 je pogreška, 0 je u redu af (); }}; predložak struct Prime_print {

// potpuna specijalizacija za završetak petlje enum {pri = 0}; praznina f () {D d = 0; }; }; #ifndef LAST #define LAST 18 #endif int main () {Prime_print a; af (); }

Ako kompajlirate ovaj program, kompajler će ispisati poruke o pogreškama kada, u Prime_print::f(), inicijalizacija d ne uspije. To se događa kada je početna vrijednost 1, jer postoji samo konstruktor za void *, a samo 0 ima valjanu pretvorbu u void*. Na primjer, na jednom prevoditelju dobivamo (između nekoliko drugih poruka) sljedeće pogreške:

unruh.cpp: 39: 14: pogreška: nema održive konverzije iz 'const int' u 'D' unruh.cpp: 39: 14: greška: nema održive konverzije iz 'const int' u 'D' unruh.cpp: 39: 14: pogreška: nema održivog pretvaranja iz 'const int' u 'D' unruh.cpp: 39: 14: pogreška: nema održivog pretvaranja iz 'const int' u 'D' unruh.cpp: 39: 14: greška: nema održivog pretvorba iz 'const int' u 'D' unruh.cpp: 39: 14: pogreška: nema održivog pretvaranja iz 'const int' u 'D' unruh.cpp: 39: 14: pogreška: nema održive pretvorbe iz 'const int' do "D"

Note: As error handling in compilers differs, some compilers might stop after printing the first error message.

The concept of C++ template metaprogramming as a serious programming tool was first made popular (and somewhat formalized) by Todd Veldhuizen in his paper “Using C++ Template Metaprograms.” Veldhuizen’s work on Blitz++ (a numeric array library for C++) also introduced many refinements and extensions to metaprogramming (and to expression template techniques).

Both the first edition of this book and Andrei Alexandrescu’s Modern C++ Design contributed to an explosion of C++ libraries exploiting template-based metaprogramming by cataloging some of the basic techniques that are still in use today. The Boost project was instrumental in bringing order to this explosion. Early on, it introduced the MPL (metaprogramming library), which defined a consistent framework for type metaprogramming made popular also through David Abrahams and Aleksey Gurtovoy’s book C++ Template Metaprogramming.

Additional important advances have been made by Louis Dionne in making metaprogramming syntactically more accessible, particularly through his Boost.Hana library. Dionne, along with Andrew Sutton, Herb Sutter, David Vandevoorde, and others are now spearheading efforts in the standardization committee to give metaprogramming first-class support in the language. An important basis for that work is the exploration of what program properties should be available through reflection; Matúš Chochlík, Axel Naumann, and David Sankel are principal contributors in that area.

John J. Barton and Lee R. Nackman illustrated how to keep track of dimensional units when performing computations. The SIunits library was a more comprehensive library for dealing with physical units developed by Walter Brown. The std::chrono component in the standard library only deals with time and dates, and was contributed by Howard Hinnant.