Ohjelmointi

Johdatus metaprogrammiin C ++: ssa

Edellinen 1 2 3 Sivu 3 Sivu 3/3
  • Tilamuuttujat: Malliparametrit
  • Silmukkarakenteet: Rekursio
  • Toteutuspolun vaalit: Ehdollisten lausekkeiden tai erikoistumisten avulla
  • Kokonaislukuaritmeettinen

Jos rekursiivisten ilmentymien määrälle ja sallittujen tilamuuttujien määrälle ei ole rajoituksia, se riittää laskemaan kaiken, mikä on laskettavissa. Se ei kuitenkaan välttämättä ole helppoa tehdä niin mallien avulla. Lisäksi, koska mallin instantiation vaatii huomattavia kääntäjäresursseja, laaja rekursiivinen instantiation hidastaa kääntäjää nopeasti tai jopa tyhjentää käytettävissä olevat resurssit. C ++ -standardi suosittelee, mutta ei määrää, että vähintään 1 024 rekursiivisten instansointien tasoa sallitaan, mikä riittää useimpiin (mutta ei varmasti kaikkiin) mallin metaprogrammitehtäviin.

Siksi käytännössä mallin metaprogrammeja tulisi käyttää säästeliäästi. On kuitenkin muutamia tilanteita, joissa ne ovat korvaamattomia työkaluna kätevien mallien toteuttamiseen. Erityisesti ne voidaan joskus piilottaa perinteisempien mallien sisätiloihin, jotta kriittisistä algoritmitoteutuksista saadaan enemmän suorituskykyä.

Rekursiivinen instantiation verrattuna rekursiivisiin malliargumentteihin

Harkitse seuraavaa rekursiivista mallia:

mallin rakenne Doublify {}; template struct Trouble {using LongType = Doublify; }; template struct Trouble {käyttäen LongType = double; }; Häiriö :: LongType ouch;

Käyttö Ongelma :: LongType ei vain laukaise rekursiivista instanssia Ongelma, Ongelma, …, Ongelma, mutta se myös välitön Kaksinkertaista yhä monimutkaisemmista tyypeistä. Taulukko kuvaa kuinka nopeasti se kasvaa.

Kasvu Ongelma :: LongType

 
Kirjoita AliasTyyppi
Ongelma :: LongTypekaksinkertainen
Ongelma :: LongTypeKaksinkertaista
Ongelma :: LongTypeKaksinkertaista<>

Kaksinkertaista>

Ongelma :: LongTypeKaksinkertaista<>

Kaksinkertaista>,

   <>

Kaksinkertaista >>

Kuten taulukosta käy ilmi, lausekkeen tyypin kuvauksen monimutkaisuus Ongelma :: LongType kasvaa räjähdysmäisesti N. Yleensä tällainen tilanne korostaa C ++ -kääntäjää jopa enemmän kuin tekevät rekursiivisia ilmentymiä, joihin ei liity rekursiivisia malliargumentteja. Yksi ongelmista on, että kääntäjä pitää esityksen tyypin sekoitetusta nimestä. Tämä sekoitettu nimi koodaa jollain tavalla tarkan mallin erikoistumisen, ja varhaisissa C ++ -toteutuksissa käytettiin koodausta, joka on suunnilleen verrannollinen malli-id: n pituuteen. Nämä kääntäjät käyttivät sitten reilusti yli 10000 merkkiä Ongelma :: LongType.

Uudemmissa C ++ -toteutuksissa otetaan huomioon se tosiasia, että sisäkkäiset malli-id: t ovat melko yleisiä nykyaikaisissa C ++ -ohjelmissa, ja käyttävät älykkäitä pakkaustekniikoita vähentääkseen merkittävästi nimikoodauksen kasvua (esimerkiksi muutama sata merkkiä Ongelma :: LongType). Nämä uudemmat kääntäjät välttävät myös sekoitetun nimen luomista, jos sellaista ei todellakaan tarvita, koska mallin ilmentymälle ei tosiasiallisesti luoda matalan tason koodia. Silti, kun kaikki muut asiat ovat tasa-arvoisia, on todennäköisesti parempi järjestää rekursiivinen instantiointi siten, että malliargumentteja ei tarvitse sijoittaa myös rekursiivisesti.

Lukuarvot verrattuna staattisiin vakioihin

C ++: n alkuaikoina luetteloarvot olivat ainoa mekanismi "todellisten vakioiden" luomiseksi (kutsutaan vakiolausekkeet) nimettyinä jäseninä luokan ilmoituksissa. Niiden avulla voit esimerkiksi määrittää a Pow3 metaprogrammin laskemaan 3: n tehot seuraavasti:

meta / pow3enum.hpp // ensisijainen malli laskeaksesi 3: n n: nneksi mallipohjalle Pow3 {enum {value = 3 * Pow3 :: value}; }; // täydellinen erikoistuminen rekursiomallin strukturointiin Pow3 {enum {value = 1}; };

C ++ 98: n standardointi otti käyttöön luokan staattisen vakion alustuslaitteiden käsitteen, jotta Pow3-metaprogrammi voisi näyttää seuraavalta:

meta / pow3const.hpp // ensisijainen malli laskettaessa 3: nnen mallin rakenne Pow3 {staattinen int const arvo = 3 * Pow3 :: arvo; }; // täydellinen erikoistuminen rekursiomallin strukturointiin Pow3 {static int const value = 1; };

Tällä versiolla on kuitenkin haittapuoli: Staattiset vakiojäsenet ovat arvoja. Joten, jos sinulla on ilmoitus, kuten

void foo (int const &);

ja välität sen metaprogrammin tuloksen:

foo (Pow3 :: arvo);

kääntäjän on läpäistävä osoite / Pow3 :: arvo, ja se pakottaa kääntäjän instantisoimaan ja varaamaan staattisen jäsenen määritelmän. Tämän seurauksena laskenta ei ole enää rajoitettu puhtaaseen "kokoamisajan" vaikutukseen.

Listausarvot eivät ole arvoja (eli niillä ei ole osoitetta). Joten kun välität ne viitteenä, staattista muistia ei käytetä. Se on melkein täsmälleen kuin jos olet välittänyt lasketun arvon kirjaimena.

C ++ 11 otettiin kuitenkin käyttöön constexpr staattiset tietojäsenet, ja ne eivät rajoitu integraalityyppeihin. Ne eivät ratkaise yllä esitettyä osoitekysymystä, mutta tuosta puutteesta huolimatta ne ovat nyt yleinen tapa tuottaa metaprogrammien tuloksia. Niillä on se etu, että niillä on oikea tyyppi (toisin kuin keinotekoinen laskentatyyppi), ja tämä tyyppi voidaan päätellä, kun staattinen jäsen ilmoitetaan automaattisen tyyppimäärittimen kanssa. C ++ 17 lisäsi inline staattisen datan jäseniä, jotka ratkaisevat yllä esitetyn osoitekysymyksen ja joita voidaan käyttää constexpr.

Metaprogrammihistoria

Varhaisin dokumentoitu esimerkki metaprogrammista oli Erwin Unruh, joka edusti sitten Siemensiä C ++ -standardikomiteassa. Hän pani merkille mallin instantointiprosessin laskennallisen täydellisyyden ja osoitti mielipiteensä kehittämällä ensimmäisen metaprogrammin. Hän käytti Metaware-kääntäjää ja houkutteli sitä julkaisemaan virhesanomia, jotka sisältäisivät peräkkäisiä alkulukuja. Tässä on koodi, joka jaettiin C ++ -komitean kokouksessa vuonna 1994 (muutettu siten, että se kokoaa nyt standardin mukaisilla kääntäjillä):

meta / unruh.cpp // alkulukujen laskenta // (muokattu alkuperäisen luvalla vuodelta 1994 Erwin Unruhilla) malli struct is_prime {enum ((p% i) && is_prime2? p: 0), i-1> :: pri); }; malli struct is_prime {enum {pri = 1}; }; malli struct is_prime {enum {pri = 1}; }; sapluuna rakenne D {D (mitätön *); }; sapluuna struct CondNull {staattinen int const-arvo = i; }; template struct CondNull {staattinen void * arvo; }; void * CondNull :: arvo = 0; sapluuna struct Prime_print {

// silmukan ensisijainen malli alkulukujen tulostamiseksi Prime_print a; enum {pri = is_prime :: pri}; void f () {D d = CondNull :: arvo;

// 1 on virhe, 0 on hieno a.f (); }}; mallin rakenne Prime_print {

// täydellinen erikoistuminen silmukan lopettamiseksi {pri = 0}; mitätön f () {Dd = 0; }; }; #ifndef LAST #define LAST 18 #endif int main () {Prime_print a; a.f (); }

Jos käännät tämän ohjelman, kääntäjä tulostaa virheilmoitukset, kun, sisään Päätulos :: f (), d: n alustus epäonnistuu. Tämä tapahtuu, kun alkuarvo on 1, koska void *: lle on vain konstruktori, ja vain 0: lla on kelvollinen muunnos muotoon mitätön *. Esimerkiksi yhdessä kääntäjässä saamme (useiden muiden viestien joukossa) seuraavat virheet:

unruh.cpp: 39: 14: virhe: ei elinkelpoista muunnosta 'const int': stä 'D': ksi unruh.cpp: 39: 14: virhe: ei elinkelpoista muunnosta 'const int': stä 'D': ksi unruh.cpp: 39: 14: virhe: ei elinkelpoista muunnosta 'const int': sta 'D': ksi unruh.cpp: 39: 14: virhe: ei elinkelpoista muunnosta 'const int': stä 'D' unruh.cpp: 39: 14: virhe: ei elinkelpoinen muuntaminen 'const int': sta 'D': ksi unruh.cpp: 39: 14: virhe: ei elinkelpoista muunnosta 'const int': stä 'D': ksi unruh.cpp: 39: 14: virhe: ei elinkelpoista muunnosta 'const int': stä kohtaan 'D'

Huomaa: Koska virheiden käsittely kääntäjissä eroaa, jotkut kääntäjät saattavat pysähtyä ensimmäisen virhesanoman tulostamisen jälkeen.

Todd Veldhuizen teki ensimmäisen kerran suosituksi (ja hieman viralliseksi) käsitteen C ++ mallin metaprogrammoinnista vakavaksi ohjelmointityökaluksi asiakirjassaan "C ++ Template Metaprograms -ohjelman käyttö". Veldhuizenin Blitz ++ -työ (numeerinen taulukkorekisteri C ++: lle) toi myös monia tarkennuksia ja laajennuksia metaprogrammointiin (ja lausekemallitekniikoihin).

Sekä kirjan ensimmäinen painos että Andrei Alexandrescu Moderni C ++ -suunnittelu osallistui C ++ -kirjastojen räjähdykseen hyödyntämällä mallipohjaista metaprogrammointia luetteloimalla joitain perustekniikoita, jotka ovat edelleen käytössä. Boost-projekti oli tärkeä tekijä tämän räjähdyksen järjestämisessä. Varhaisessa vaiheessa se esitteli MPL: n (metaprogramming library), joka määritteli johdonmukaisen kehyksen tyypin metaprogrammointi tehnyt suosituksi myös David Abrahamsin ja Aleksey Gurtovoyn kirjan kautta C ++ Template Metaprogramming.

Louis Dionne on tehnyt muita merkittäviä edistysaskeleita metaprogrammien tekemisessä syntaktisesti helpommin saavutettavaksi, erityisesti hänen Boost.Hana-kirjastonsa kautta. Dionne yhdessä Andrew Suttonin, Herb Sutterin, David Vandevoorden ja muiden kanssa johtaa nyt standardointikomiteassa pyrkimyksiä antaa ensiluokkaista tukea metaprogrammoinnille kielellä. Tärkeä perusta tälle työlle on tutkia, mitä ohjelman ominaisuuksia pitäisi olla käytettävissä pohdinnan kautta; Matúš Chochlík, Axel Naumann ja David Sankel ovat tärkeimmät avustajat tällä alueella.

John J.Barton ja Lee R.Nackman havainnollistivat kuinka seurata mittayksiköitä suoritettaessa laskelmia. SIunits-kirjasto oli Walter Brownin kehittämä kattavampi kirjasto fyysisten yksiköiden käsittelemiseksi. vakio :: chrono komponentti vakiokirjastossa käsittelee vain aikaa ja päivämääriä, ja sen on kirjoittanut Howard Hinnant.