Programovanie

Úvod do metaprogramovania v C ++

Predchádzajúca 1 2 3 Strana 3 Strana 3 z 3
  • Stavové premenné: parametre šablóny
  • Smyčkové konštrukcie: Rekurziou
  • Voľba spôsobov vykonania: Použitím podmienených výrazov alebo špecializácií
  • Celé číslo aritmetické

Ak neexistujú žiadne obmedzenia týkajúce sa množstva rekurzívnych inštancií a počtu povolených stavových premenných, stačí to na vypočítanie všetkého, čo je vypočítateľné. Môže to však byť nepríjemné urobiť pomocou šablón. Ďalej, pretože inštancia šablóny vyžaduje značné zdroje kompilátora, rozsiahla rekurzívna inštancia rýchlo spomalí kompilátor alebo dokonca vyčerpá dostupné zdroje. Štandard C ++ odporúča, ale nenariaďuje, aby bolo povolených minimálne 1 024 úrovní rekurzívnych inštancií, čo je dostatočné pre väčšinu (ale určite nie všetkých) úloh metaprogramovania šablón.

V praxi by sa teda mali metaprogramy šablón používať s mierou. Existuje však niekoľko situácií, keď sú nenahraditeľné ako nástroj na implementáciu pohodlných šablón. Najmä môžu byť niekedy skryté vo vnútorných priestoroch konvenčnejších šablón, aby vytlačili vyšší výkon z implementácií kritických algoritmov.

Rekurzívna inštancia verzus rekurzívne argumenty šablóny

Zvážte nasledujúcu rekurzívnu šablónu:

šablónová štruktúra Doublify {}; šablóna struct Trouble {using LongType = Doublify; }; šablóna struct Trouble {using LongType = double; }; Problém :: LongType ouch;

Použitie Problém :: LongType nielenže spúšťa rekurzívnu inštanciu súboru Problémy, Problémy, …, Problémy, ale tiež vytvára inštancie Zdvojnásobiť cez čoraz zložitejšie typy. Tabuľka ilustruje, ako rýchlo rastie.

Rast spoločnosti Problém :: LongType

 
Typ AliasPodkladový typ
Problém :: LongTypedvojitý
Problém :: LongTypeZdvojnásobiť
Problém :: LongTypeZdvojnásobiť<>

Zdvojnásobiť>

Problém :: LongTypeZdvojnásobiť<>

Zdvojnásobiť>,

   <>

Zdvojnásobiť >>

Ako ukazuje tabuľka, zložitosť popisu typu výrazu Problém :: LongType rastie exponenciálne s N. Všeobecne takáto situácia zdôrazňuje kompilátor C ++ ešte viac ako rekurzívne inštancie, ktoré neobsahujú argumenty rekurzívnej šablóny. Jedným z problémov tu je, že kompilátor udržiava reprezentáciu pozmeneného názvu typu. Tento pokazený názov nejakým spôsobom kóduje presnú špecializáciu šablón a skoré implementácie C ++ používali kódovanie, ktoré je zhruba úmerné dĺžke id šablóny. Títo prekladači potom použili na viac ako 10 000 znakov Problém :: LongType.

Novšie implementácie C ++ zohľadňujú skutočnosť, že vnorené identifikátory šablón sú v moderných programoch C ++ pomerne bežné, a využívajú techniky šikovnej kompresie na výrazné zníženie rastu kódovania mien (napríklad niekoľko stoviek znakov pre Problém :: LongType). Tieto novšie kompilátory sa tiež vyhýbajú generovaniu pozmeneného názvu, ak žiadny skutočne nie je potrebný, pretože pre inštanciu šablóny sa v skutočnosti negeneruje žiadny nízkoúrovňový kód. Aj keď sú všetky ostatné veci rovnaké, je pravdepodobne lepšie organizovať rekurzívne inštancie tak, aby argumenty šablón nemuseli byť tiež rekurzívne vnorené.

Hodnoty výčtu verzus statické konštanty

V začiatkoch C ++ boli hodnoty výčtu jediným mechanizmom na vytvorenie „skutočných konštánt“ (tzv konštanty-výrazy) ako menovaní členovia v deklaráciách triedy. Pomocou nich môžete napríklad definovať a Pow3 metaprogram na výpočet mocnin 3 takto:

meta / pow3enum.hpp // primárna šablóna na výpočet 3 až n-tej šablóny struct Pow3 {enum {hodnota = 3 * Pow3 :: hodnota}; }; // úplná špecializácia na ukončenie šablóny rekurzie struct Pow3 {enum {value = 1}; };

Štandardizácia jazyka C ++ 98 predstavila koncept inicializátorov statických konštánt v triede, aby metaprogram Pow3 mohol vyzerať takto:

meta / pow3const.hpp // primárna šablóna na výpočet 3 až n-tej šablóny struct Pow3 {static int const value = 3 * Pow3 :: value; }; // úplná špecializácia na ukončenie šablóny rekurzie struct Pow3 {static int const value = 1; };

Táto verzia však má jednu nevýhodu: Členmi statickej konštanty sú hodnoty. Ak teda máte vyhlásenie ako napr

void foo (int const &);

a odovzdáte mu výsledok metaprogramu:

foo (Pow3 :: hodnota);

kompilátor musí zložiť adresa z Pow3 :: hodnota, a to núti kompilátor vytvoriť inštanciu a prideliť definíciu pre statický člen. Výsledkom je, že výpočet sa už neobmedzuje iba na čistý efekt „kompilácie“.

Hodnoty výčtu nie sú hodnotami (to znamená, že nemajú adresu). Keď ich teda predáte odkazom, nepoužíva sa žiadna statická pamäť. Je to takmer presne, akoby ste vypočítanú hodnotu odovzdali ako literál.

C ++ 11 je však zavedený constexpr statické dátové členy a tie sa neobmedzujú iba na integrálne typy. Neriešia vyššie uvedený problém s adresou, ale napriek tomuto nedostatku sú dnes bežným spôsobom, ako produkovať výsledky metaprogramov. Majú výhodu v tom, že majú správny typ (na rozdiel od typu umelého výčtu) a tento typ je možné odvodiť, keď je statický člen deklarovaný so špecifikátorom automatického typu. C ++ 17 pridal vložené statické dátové členy, ktoré riešia vyššie uvedený problém s adresou, a je možné ich používať s constexpr.

História metaprogramovania

Prvým zdokumentovaným príkladom metaprogramu bol Erwin Unruh, ktorý potom zastupoval spoločnosť Siemens v normalizačnom výbore C ++. Zaznamenal výpočtovú úplnosť procesu inštancie šablóny a svoj názor demonštroval vyvinutím prvého metaprogramu. Použil kompilátor Metaware a prinútil ho vydávať chybové správy, ktoré budú obsahovať postupné prvočísla. Tu je kód, ktorý bol distribuovaný na schôdzi výboru C ++ v roku 1994 (upravený tak, aby sa teraz kompiloval na kompilátoroch vyhovujúcich štandardom):

meta / unruh.cpp // výpočet prvočísla // (upravený so súhlasom pôvodného z roku 1994 Erwin Unruh) šablóna struct is_prime {enum ((p% i) && is_prime2? p: 0), i-1> :: pri); }; template struct is_prime {enum {pri = 1}; }; template struct is_prime {enum {pri = 1}; }; šablóna štruktúra D {D (void *); }; šablóna struct CondNull {static int const value = i; }; šablóna struct CondNull {static void * hodnota; }; void * CondNull :: value = 0; šablóna struct Prime_print {

// primárna šablóna pre slučku na tlač prvočísel Prime_print a; enum {pri = is_prime :: pri}; void f () {D d = CondNull :: hodnota;

// 1 je chyba, 0 je v poriadku a.f (); }}; template struct Prime_print {

// úplná špecializácia na ukončenie cyklu enum {pri = 0}; neplatné f () {D d = 0; }; }; #ifndef POSLEDNÉ # definovať POSLEDNÉ 18 #endif int main () {Prime_print a; a.f (); }

Ak kompilujete tento program, kompilátor bude tlačiť chybové správy, keď bude v Prime_print :: f (), inicializácia d zlyhá. To sa stane, keď je počiatočná hodnota 1, pretože pre void * existuje iba konštruktor a iba 0 má platnú konverziu na neplatné *. Napríklad na jednom kompilátore dostaneme (okrem niekoľkých ďalších správ) nasledujúce chyby:

unruh.cpp: 39: 14: chyba: žiadna životaschopná konverzia z „const int“ na „D“ unruh.cpp: 39: 14: chyba: žiadna životaschopná konverzia z „const int“ na „D“ unruh.cpp: 39: 14: chyba: žiadna životaschopná konverzia z „const int“ na „D“ unruh.cpp: 39: 14: chyba: žiadna životaschopná konverzia z „const int“ na „D“ unruh.cpp: 39: 14: chyba: žiadna životaschopnosť konverzia z „const int“ na „D“ unruh.cpp: 39: 14: chyba: žiadna životaschopná konverzia z „const int“ na „D“ unruh.cpp: 39: 14: chyba: žiadna životaschopná konverzia z „const int“ do „D“

Poznámka: Pretože sa spracovanie chýb v kompilátoroch líši, niektoré kompilátory sa môžu po vytlačení prvého chybového hlásenia zastaviť.

Koncept metaprogramovania šablón C ++ ako seriózneho programovacieho nástroja po prvý raz spopularizoval (a trochu formalizoval) Todd Veldhuizen vo svojom príspevku „Používanie metaprogramov šablón C ++“. Veldhuizenova práca na Blitz ++ (numerická knižnica polí pre C ++) taktiež priniesla mnoho vylepšení a rozšírení metaprogramovania (a techník šablón výrazov).

Prvé vydanie tejto knihy aj Andrej Alexandrescu Moderný dizajn v C ++ prispel k explózii knižníc C ++ využívajúcich metaprogramovanie na základe šablón katalogizáciou niektorých základných techník, ktoré sa dodnes používajú. Projekt Boost pomohol dosiahnuť poriadok v tejto explózii. Hneď na začiatku predstavila MPL (metaprogramming library), ktorá definovala konzistentný rámec pre metaprogramovanie typu populárne aj prostredníctvom knihy Davida Abrahamsa a knihy Aleksey Gurtovoya Metaprogramovanie šablón v C ++.

Louis Dionne urobil ďalšie dôležité pokroky v oblasti syntaktickej dostupnosti metaprogramovania, najmä prostredníctvom svojej knižnice Boost.Hana. Dionne spolu s Andrewom Suttonom, Herbom Sutterom, Davidom Vandevoordom a ďalšími dnes stojí v čele normalizačného výboru pri poskytovaní prvotriednej podpory metaprogramovaniu v jazyku. Dôležitým základom pre túto prácu je preskúmanie toho, aké vlastnosti programu by mali byť dostupné prostredníctvom reflexie; Matúš Chochlík, Axel Naumann a David Sankel sú hlavnými prispievateľmi v tejto oblasti.

John J. Barton a Lee R. Nackman ilustrovali, ako sledovať rozmerové jednotky pri výpočtoch. Knižnica SIunits bola komplexnejšou knižnicou pre prácu s fyzickými jednotkami vyvinutou Walterom Brownom. The std :: chrono komponent v štandardnej knižnici sa zaoberá iba časom a dátumami a prispel ním Howard Hinnant.

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