Programování

Úvod do metaprogramování v C ++

Předchozí 1 2 3 Strana 3 Stránka 3 ze 3
  • Stavové proměnné: Parametry šablony
  • Konstrukce smyčky: Rekurzí
  • Volba cest provedení: Použitím podmíněných výrazů nebo specializací
  • Celé číslo aritmetické

Pokud neexistují žádná omezení počtu rekurzivních instancí a počtu povolených stavových proměnných, je to dostatečné k výpočtu všeho, co je vypočítatelné. Může to však být nevhodné pomocí šablon. Kromě toho, protože instance šablony vyžaduje značné prostředky kompilátoru, rozsáhlá rekurzivní instance rychle zpomalí kompilátor nebo dokonce vyčerpá dostupné prostředky. Standard C ++ doporučuje, ale nenutí, aby bylo povoleno minimálně 1024 úrovní rekurzivních instancí, což je dostatečné pro většinu (ale určitě ne všech) úkolů metaprogramování šablon.

V praxi by tedy metaprogramy šablon měly být používány střídmě. Existuje několik situací, kdy jsou nenahraditelné jako nástroj k implementaci pohodlných šablon. Zejména mohou být někdy skryty ve vnitřnostech konvenčnějších šablon, aby vytlačily vyšší výkon z implementací kritických algoritmů.

Rekurzivní instance proti argumentům rekurzivní šablony

Zvažte následující rekurzivní šablonu:

template struct Doublify {}; problém se strukturou šablony {pomocí LongType = Doublify; }; Problém se strukturou šablony {pomocí LongType = double; }; Trouble :: LongType ouch;

Použití Problém :: LongType nejenže spouští rekurzivní vytvoření instance Problémy, Problémy, …, Problémy, ale také vytváří instanci Zdvojnásobit přes stále složitější typy. Tabulka ukazuje, jak rychle roste.

Růst Problém :: LongType

 
Zadejte AliasZákladní typ
Problém :: LongTypedvojnásobek
Problém :: LongTypeZdvojnásobit
Problém :: LongTypeZdvojnásobit<>

Zdvojnásobit>

Problém :: LongTypeZdvojnásobit<>

Zdvojnásobit>,

   <>

Zdvojnásobit >>

Jak ukazuje tabulka, složitost popisu typu výrazu Problém :: LongType roste exponenciálně s N. Obecně taková situace zdůrazňuje kompilátor C ++ ještě více než rekurzivní instance, které nezahrnují argumenty rekurzivní šablony. Jedním z problémů zde je, že kompilátor udržuje reprezentaci pozměněného názvu typu. Tento zkreslený název nějakým způsobem kóduje přesnou specializaci šablony a časné implementace C ++ používaly kódování, které je zhruba úměrné délce id šablony. Tito překladači poté použili více než 10 000 znaků Problém :: LongType.

Novější implementace C ++ berou v úvahu skutečnost, že vnořené šablony id jsou v moderních programech C ++ poměrně běžné a využívají chytré techniky komprese, aby výrazně snížily nárůst kódování názvů (například několik stovek znaků pro Problém :: LongType). Tito novější kompilátoři se také vyhýbají generování pozměněného názvu, pokud žádný není skutečně potřeba, protože pro instanci šablony se ve skutečnosti nevygeneruje žádný nízkoúrovňový kód. Přesto, že jsou všechny ostatní věci stejné, je pravděpodobně lepší uspořádat rekurzivní instanci takovým způsobem, že argumenty šablony nemusí být také rekurzivně vnořeny.

Hodnoty výčtu versus statické konstanty

V počátcích C ++ byly hodnoty výčtu jediným mechanismem k vytvoření „skutečných konstant“ (tzv konstantní výrazy) jako pojmenovaní členové v deklaraci třídy. S nimi můžete například definovat a Pow3 metaprogram pro výpočet mocnin 3 následujícím způsobem:

meta / pow3enum.hpp // primární šablona pro výpočet 3 k N-té šabloně struktury Pow3 {enum {hodnota = 3 * Pow3 :: hodnota}; }; // úplná specializace pro ukončení rekurzní šablony struct Pow3 {enum {value = 1}; };

Standardizace C ++ 98 zavedla koncept inicializátorů statické konstanty ve své třídě, takže metaprogram Pow3 mohl vypadat takto:

meta / pow3const.hpp // primární šablona pro výpočet 3 k N-té šabloně struct Pow3 {static int const value = 3 * Pow3 :: value; }; // úplná specializace pro ukončení rekurzní šablony struct Pow3 {static int const value = 1; };

U této verze však existuje nevýhoda: Členy statické konstanty jsou hodnoty. Pokud tedy máte prohlášení, jako je

void foo (int const &);

a předáte jej jako výsledek metaprogramu:

foo (Pow3 :: hodnota);

překladač musí předat adresa z Pow3 :: hodnota, a to nutí kompilátor vytvořit instanci a přidělit definici statického člena. Výsledkem je, že výpočet již není omezen na čistý efekt „kompilace“.

Hodnoty výčtu nejsou hodnotami (tj. Nemají adresu). Když je tedy předáte odkazem, nepoužije se žádná statická paměť. Je to téměř přesně, jako byste vypočítanou hodnotu předali jako literál.

C ++ 11, nicméně, představil constexpr statické datové členy a ty nejsou omezeny na integrální typy. Nevyřeší výše uvedený problém s adresou, ale i přes tento nedostatek jsou nyní běžným způsobem, jak vytvářet výsledky metaprogramů. Mají tu výhodu, že mají správný typ (na rozdíl od typu umělého výčtu) a tento typ lze odvodit, když je statický člen deklarován se specifikátorem automatického typu. C ++ 17 přidal vložené statické datové členy, které řeší výše uvedený problém s adresou a lze je použít constexpr.

Historie metaprogramování

Nejdříve zdokumentovaným příkladem metaprogramu byl Erwin Unruh, který poté zastupoval společnost Siemens ve standardizačním výboru C ++. Zaznamenal výpočetní úplnost procesu vytváření instance šablony a svůj názor demonstroval vývojem prvního metaprogramu. Použil kompilátor Metaware a přiměl jej k vydávání chybových zpráv, které by obsahovaly postupná prvočísla. Zde je kód, který byl rozeslán na schůzi výboru C ++ v roce 1994 (upravený tak, aby se nyní kompiloval na standardních kompilátorech):

meta / unruh.cpp // výpočet prvočísla // (upraveno se svolením originálu z roku 1994 Erwin Unruh) šablona 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}; }; šablona struktura D {D (void *); }; šablona struct CondNull {static int const value = i; }; template struct CondNull {static void * value; }; void * CondNull :: value = 0; šablona struct Prime_print {

// primární šablona pro smyčku pro tisk prvočísel Prime_print a; enum {pri = is_prime :: pri}; void f () {D d = CondNull :: value;

// 1 je chyba, 0 je v pořádku a.f (); }}; template struct Prime_print {

// úplná specializace pro ukončení enum smyčky {pri = 0}; void f () {D d = 0; }; }; #ifndef POSLEDNÍ # definovat POSLEDNÍ 18 #endif int main () {Prime_print a; a.f (); }

Pokud kompilujete tento program, kompilátor vytiskne chybové zprávy, když v Prime_print :: f (), inicializace d selže. To se stane, když je počáteční hodnota 1, protože pro void * existuje pouze konstruktor a pouze 0 má platný převod na neplatné *. Například na jednom kompilátoru dostaneme (kromě několika dalších zpráv) následující chyby:

unruh.cpp: 39: 14: chyba: žádná životaschopná konverze z „const int“ na „D“ unruh.cpp: 39: 14: chyba: žádná životaschopná konverze z „const int“ na „D“ unruh.cpp: 39: 14: chyba: žádná životaschopná konverze z „const int“ na „D“ unruh.cpp: 39: 14: chyba: žádná životaschopná konverze z „const int“ na „D“ unruh.cpp: 39: 14: chyba: žádná životaschopnost převod z 'const int' na 'D' unruh.cpp: 39: 14: chyba: žádný životaschopný převod z 'const int' na 'D' unruh.cpp: 39: 14: chyba: žádný životaschopný převod z 'const int' do 'D'

Poznámka: Jelikož se zpracování chyb v kompilátorech liší, mohou se některé kompilátory po vytištění první chybové zprávy zastavit.

Koncept metaprogramování šablon v C ++ jako seriózního programovacího nástroje poprvé oblíbil (a poněkud formalizoval) Todd Veldhuizen ve své práci „Používání metaprogramů šablon v C ++“. Veldhuizenova práce na Blitz ++ (numerická knihovna polí pro C ++) také představila mnoho vylepšení a rozšíření metaprogramování (a technik výrazových šablon).

První vydání této knihy i Andrei Alexandrescu Moderní design v C ++ přispěl k explozi knihoven C ++ využívajících metaprogramování na základě šablon katalogizací některých základních technik, které se dodnes používají. Projekt Boost pomohl nastolit pořádek při této explozi. Hned na začátku představila MPL (metaprogramovací knihovnu), která definovala konzistentní rámec pro metaprogramování typu populární také díky knize Davida Abrahamse a Aleksey Gurtovoye Metaprogramování šablony C ++.

Další důležité pokroky učinil Louis Dionne při zpřístupňování syntaktického metaprogramování, zejména prostřednictvím své knihovny Boost.Hana. Dionne spolu s Andrewem Suttonem, Herbem Sutterem, Davidem Vandevoordem a dalšími nyní stojí v čele standardizačního výboru o poskytování prvotřídní podpory metaprogramování v tomto jazyce. Důležitým základem pro tuto práci je prozkoumání toho, jaké vlastnosti programu by měly být dostupné prostřednictvím reflexe; Matúš Chochlík, Axel Naumann a David Sankel jsou hlavními přispěvateli v této oblasti.

John J. Barton a Lee R. Nackman ilustrovali, jak sledovat dimenzionální jednotky při provádění výpočtů. Knihovna SIunits byla komplexnější knihovnou pro práci s fyzickými jednotkami vyvinutou Walterem Brownem. The std :: chrono komponenta ve standardní knihovně se zabývá pouze časem a daty a přispěl k ní Howard Hinnant.