Programiranje

Uvod v metaprogramiranje v jeziku C ++

Prejšnja 1 2 3 Stran 3 Stran 3 od 3
  • Spremenljivke stanja: parametri predloge
  • Konstrukcije zanke: Skozi rekurzijo
  • Izvolilne poti izvršitve: z uporabo pogojnih izrazov ali specializacij
  • Celoštevilska aritmetika

Če ni omejitev za količino rekurzivnih primerkov in število dovoljenih spremenljivk stanja, to zadostuje za izračun vsega, kar je izračunano. Vendar morda ni priročno, če to storite s predlogami. Poleg tega, ker instantacija predloge zahteva znatna sredstva prevajalnika, obsežna rekurzivna instancacija hitro upočasni prevajalnik ali celo izčrpa razpoložljive vire. Standard C ++ priporoča, vendar ne zahteva, da je dovoljenih najmanj 1.024 nivojev rekurzivnih instanc, kar zadostuje za večino (vendar vsekakor ne) nalog metaprogramiranja predlog.

Tako bi bilo treba v praksi metaprograme predloge uporabljati zmerno. Obstaja pa nekaj primerov, ko so nenadomestljivi kot orodje za izvajanje priročnih predlog. Zlasti jih je mogoče včasih skriti v notranjosti bolj običajnih predlog, da iztisnejo večjo zmogljivost iz kritičnih implementacij algoritmov.

Rekurzivna instancacija v primerjavi z rekurzivnimi argumenti predloge

Upoštevajte naslednjo rekurzivno predlogo:

predloga struct Doublify {}; Predloga struct Trouble {using LongType = Doublify; }; Predloga struct Trouble {using LongType = double; }; Težava :: LongType ouch;

Uporaba Težava :: LongType ne samo, da sproži rekurzivni primerek Težave, Težave, …, Težave, vendar tudi ustvari primerek Dvojite nad vse bolj zapletenimi vrstami. Tabela prikazuje, kako hitro raste.

Rast Težava :: LongType

 
Vnesite vzdevekOsnovni tip
Težava :: LongTypedvojno
Težava :: LongTypeDvojite
Težava :: LongTypeDvojite<>

Dvojite>

Težava :: LongTypeDvojite<>

Doublify>,

   <>

Dvojite >>

Kot prikazuje tabela, je zapletenost opisov tipa izraza Težava :: LongType raste eksponentno z N. Na splošno takšna situacija poudarja prevajalnik C ++ celo bolj kot rekurzivne instancacije, ki ne vključujejo rekurzivnih argumentov predloge. Tu je ena od težav, da prevajalnik obdrži predstavitev pokvarjenega imena za tip. To pokvarjeno ime na nek način kodira natančno specializacijo predloge, zgodnje izvedbe C ++ pa so uporabljale kodiranje, ki je približno sorazmerno z dolžino id-a predloge. Ti prevajalniki so nato uporabili več kot 10.000 znakov Težava :: LongType.

Novejše izvedbe C ++ upoštevajo dejstvo, da so ugnezdene identifikacijske predloge v sodobnih programih C ++ dokaj pogoste, in s pametnimi tehnikami stiskanja znatno zmanjšajo rast kodiranja imen (na primer nekaj sto znakov za Težava :: LongType). Ti novejši prevajalniki se tudi izogibajo ustvarjanju pokvarjenega imena, če nobeno dejansko ni potrebno, ker za primerek predloge dejansko ni ustvarjena koda na nizki ravni. Kljub temu pa je pri vseh enakih pogojih verjetno bolje, da rekurzivno instanciranje organiziramo tako, da argumentov predloge ni treba tudi gnezditi rekurzivno.

Vrednosti štetja v primerjavi s statičnimi konstantami

V prvih dneh C ++ so bile enumeracijske vrednosti edini mehanizem za ustvarjanje "resničnih konstant" (imenovane konstantni izrazi) kot imenovani člani v izjavah razredov. Z njimi lahko na primer določite a Pow3 metaprogram za izračun potenc 3, kot sledi:

meta / pow3enum.hpp // primarna predloga za izračun 3 do N-te strukture predloge Pow3 {enum {vrednost = 3 * Pow3 :: vrednost}; }; // popolna specializacija za zaključek predloge rekurzije struct Pow3 {enum {value = 1}; };

Standardizacija C ++ 98 je predstavila koncept iniciatorjev statične konstante v svojem razredu, tako da je lahko metaprogram Pow3 videti tako:

meta / pow3const.hpp // primarna predloga za izračun 3 do N-te strukture Pow3 {static int const value = 3 * Pow3 :: value; }; // popolna specializacija za konec predloge rekurzije struct Pow3 {static int const value = 1; };

Vendar ima ta različica slabost: Statični konstantni člani so vrednosti. Torej, če imate izjavo, kot je

void foo (int const &);

in mu posredujete rezultat metaprograma:

foo (Pow3 :: value);

prevajalnik mora prenesti datoteko naslov od Pow3 :: vrednostin to prisili prevajalnik, da ustvari primer in dodeli definicijo za statičnega člana. Kot rezultat, izračun ni več omejen na zgolj učinek "časa prevajanja".

Vrednosti štetja niso lvalues ​​(to pomeni, da nimajo naslova). Torej, ko jih prenesete po sklicu, se ne uporablja statični pomnilnik. Skoraj natanko tako, kot da ste izračunano vrednost predali kot dobesedno.

Uveden pa je bil C ++ 11 constexpr statični člani podatkov in ti niso omejeni na integralne tipe. Ne rešujejo zgoraj omenjenega vprašanja naslova, vendar so kljub tej pomanjkljivosti zdaj pogost način za ustvarjanje rezultatov metaprogramov. Prednost imajo pravilno vrsto (v nasprotju z umetno našteto vrsto) in to vrsto je mogoče ugotoviti, ko je statični član deklariran s specifikatorjem samodejnega tipa. C ++ 17 je dodal vstavljene člane statičnih podatkov, ki resnično rešujejo zgoraj navedeno težavo z naslovom in jih je mogoče uporabljati z constexpr.

Zgodovina metaprogramiranja

Najzgodnejši dokumentirani primer metaprograma je bil Erwin Unruh, ki je takrat zastopal Siemens v odboru za standardizacijo C ++. Opozoril je na računsko popolnost postopka instanciranja predloge in svojo trditev pokazal z razvojem prvega metaprograma. Uporabil je prevajalnik Metaware in ga nagovoril k izdaji sporočil o napakah, ki bi vsebovale zaporedna praštevila. Tu je koda, ki je bila razposlana na seji odbora C ++ leta 1994 (spremenjena tako, da se zdaj prevaja na standardnih skladnih prevajalnikih):

meta / unruh.cpp // izračun prostega števila // (spremenjeno z dovoljenjem izvirnika iz leta 1994 Erwin Unruh) struct is_prime {enum ((p% i) && is_prime2? p: 0), i-1> :: pri); }; predloga struct is_prime {enum {pri = 1}; }; predloga struct is_prime {enum {pri = 1}; }; predloga struct D {D (void *); }; predloga struct CondNull {statična vrednost int const = i; }; predloga struct CondNull {statična void * vrednost; }; void * CondNull :: value = 0; predloga struct Prime_print {

// primarna predloga za zanko za tiskanje praštevil Prime_print a; enum {pri = is_prime :: pri}; void f () {D d = CondNull :: vrednost;

// 1 je napaka, 0 je v redu a.f (); }}; predloga struct Prime_print {

// popolna specializacija za konec zanke enum {pri = 0}; praznina f () {D d = 0; }; }; #ifndef LAST #define LAST 18 #endif int main () {Prime_print a; a.f (); }

Če prevedete ta program, bo prevajalnik natisnil sporočila o napakah, ko bo v Prime_print :: f (), inicializacija d ne uspe. To se zgodi, ko je začetna vrednost 1, ker obstaja samo konstruktor za void * in samo 0 ima veljavno pretvorbo v praznina *. Na primer, pri enem prevajalniku dobimo (med več drugimi sporočili) naslednje napake:

unruh.cpp: 39: 14: napaka: ni izvedljive pretvorbe iz 'const int' v 'D' unruh.cpp: 39: 14: napaka: ni izvedljive pretvorbe iz 'const int' v 'D' unruh.cpp: 39: 14: napaka: ni izvedljive pretvorbe iz 'const int' v 'D' unruh.cpp: 39: 14: napaka: ni izvedljive pretvorbe iz 'const int' v 'D' unruh.cpp: 39: 14: napaka: ni izvedljive pretvorba iz 'const int' v 'D' unruh.cpp: 39: 14: napaka: ni izvedljive pretvorbe iz 'const int' v 'D' unruh.cpp: 39: 14: napaka: ni izvedljive pretvorbe iz 'const int' do 'D'

Opomba: Ker se obravnavanje napak v prevajalnikih razlikuje, se lahko nekateri prevajalniki ustavijo po tiskanju prvega sporočila o napaki.

Koncept metaprogramiranja predloge C ++ kot resnega programskega orodja je prvi objavil (in nekoliko formaliziral) Todd Veldhuizen v prispevku "Uporaba metaprogramov predlog C ++". Veldhuizenovo delo na Blitz ++ (knjižnica numeričnih matrik za C ++) je prav tako uvedlo številne izboljšave in razširitve metaprogramiranja (in tehnik predloge izraza).

Tako prva izdaja te knjige kot Andreja Alexandrescuja Sodobno oblikovanje C ++ prispeval k eksploziji knjižnic C ++, ki izkoriščajo metaprogramiranje na osnovi predlog, s katalogizacijo nekaterih osnovnih tehnik, ki so še danes v uporabi. Projekt Boost je bil ključen za vzpostavitev reda v tej eksploziji. Že na začetku je predstavil MPL (knjižnico metaprogramiranja), ki je opredelil dosleden okvir za tip metaprogramiranje priljubljena tudi po knjigi Davida Abrahamsa in Alekseja Gurtovoja Metaprogramiranje predlog C ++.

Louis Dionne je dosegel dodaten pomemben napredek pri sintaksično dostopnejšem metaprogramiranju, zlasti prek svoje knjižnice Boost.Hana. Dionne, skupaj z Andrewom Suttonom, Herbom Sutterjem, Davidom Vandevoordejem in drugimi, zdaj vodijo prizadevanja v odboru za standardizacijo, da bi metaprogramirali prvovrstno podporo v jeziku. Pomembna podlaga za to delo je raziskati, katere lastnosti programa bi morale biti na voljo z razmislekom; Matúš Chochlík, Axel Naumann in David Sankel so glavni sodelavci na tem področju.

John J. Barton in Lee R. Nackman sta ponazorila, kako slediti dimenzijskim enotam pri izvajanju izračunov. Knjižnica SIunits je bila obsežnejša knjižnica za obravnavo fizičnih enot, ki jo je razvil Walter Brown. The std :: chrono komponenta v standardni knjižnici se ukvarja samo s časom in datumi, prispeval pa jo je Howard Hinnant.

$config[zx-auto] not found$config[zx-overlay] not found