Programavimas

Įvadas į metaprogramavimą C ++

Ankstesnis 1 2 3 3 puslapis 3 puslapis iš 3
  • Būsenos kintamieji: šablono parametrai
  • Kilpos konstrukcijos: per rekursiją
  • Rinkimai vykdymo keliu: naudojant sąlygines išraiškas ar specializacijas
  • Sveika sveikoji aritmetika

Jei rekursijų pavyzdžių kiekiui ir leidžiamų būsenos kintamųjų skaičiui nėra jokių apribojimų, to pakanka apskaičiuoti viską, kas yra apskaičiuojama. Tačiau gali būti nepatogu tai padaryti naudojant šablonus. Be to, kadangi šablonų paruošimui reikalingi dideli kompiliatoriaus ištekliai, platus rekursinis momentinis kūrimas greitai sulėtina kompiliatoriaus darbą arba netgi išeikvoja turimus išteklius. C ++ standartas rekomenduoja, bet neįpareigoja, kad būtų leidžiama mažiausiai 1024 rekursinių momentų lygių, o to pakanka daugumai (bet tikrai ne visiems) šablonų metaprogramavimo užduotims atlikti.

Taigi praktiškai šabloninės metaprogramos turėtų būti naudojamos taupiai. Tačiau yra keletas situacijų, kai jos yra nepakeičiamos kaip įrankis patogiems šablonams įgyvendinti. Visų pirma, jie kartais gali būti paslėpti įprastesnių šablonų vidiniuose kraštuose, kad iš kritinių algoritmų diegimų būtų išgauta daugiau našumo.

Rekursinis momentinis palyginimas su rekursyviais šablonų argumentais

Apsvarstykite šį rekursinį šabloną:

šablono struktūra „Doublify“ {}; Šablono struktūra Trouble {using LongType = Doublify; }; template struct Trouble {naudojant LongType = double; }; Bėda :: LongType ouch;

Panaudojimas Bėda :: „LongType“ ne tik suaktyvina rekursyvų Bėda, Bėda, …, Bėda, bet tai ir akimirksniu Abejokite per vis sudėtingesnius tipus. Lentelėje parodyta, kaip greitai ji auga.

Augimas Bėda :: „LongType“

 
Įveskite slapyvardįPagrindo tipas
Bėda :: „LongType“dvigubai
Bėda :: „LongType“Abejokite
Bėda :: „LongType“Abejokite<>

Dvigubai>

Bėda :: „LongType“Abejokite<>

Dvigubinti>,

   <>

Dvigubinkite >>

Kaip parodyta lentelėje, išraiškos tipo aprašymo sudėtingumas Bėda :: „LongType“ auga eksponentiškai su N. Apskritai tokia situacija dar labiau pabrėžia C ++ kompiliatorių, nei daro rekursines instancijas, kuriose nėra rekursinių šablonų argumentų. Viena iš problemų čia yra ta, kad kompiliatorius išlaiko rūšies pavadinimo pavadinimą. Šis supainiotas pavadinimas tam tikru būdu koduoja tikslią šablonų specializaciją, o ankstyvosiose C ++ versijose buvo naudojamas šifravimas, kuris yra maždaug proporcingas šablono ID ilgiui. Tada šie kompiliatoriai naudojo gerokai daugiau nei 10 000 simbolių Bėda :: „LongType“.

Naujesniuose C ++ diegimuose atsižvelgiama į tai, kad įdėtos šablonų id yra gana paplitusios šiuolaikinėse C ++ programose, ir naudoja sumanius glaudinimo metodus, kad žymiai sumažintų vardų kodavimo augimą (pavyzdžiui, keli šimtai simbolių Bėda :: „LongType“). Šie naujesni kompiliatoriai taip pat vengia sugeneruoto pavadinimo generavimo, jei jo iš tikrųjų nereikia, nes šablono egzemplioriui iš tikrųjų nesukuriamas žemo lygio kodas. Vis dėlto, jei visi kiti dalykai yra lygūs, tikriausiai pageidautina organizuoti rekursyvųjį momentinį pavyzdį taip, kad šablonų argumentų nereikėtų taip pat įdėti rekursyviai.

Surašymo reikšmės, palyginti su statinėmis konstantomis

Pirmosiomis C ++ dienomis surašymo reikšmės buvo vienintelis mechanizmas, sukuriantis „tikrąsias konstantas“ (vad pastovios išraiškos) kaip klasių deklaracijose įvardyti nariai. Su jais galėtumėte, pavyzdžiui, apibrėžti a Pow3 metaprograma apskaičiuoti 3 galias taip:

meta / pow3enum.hpp // pirminis šablonas 3 skaičiavimui į N-ąjį šabloną struct Pow3 {enum {value = 3 * Pow3 :: value}; }; // visa specializacija baigti rekursijos šabloną struct Pow3 {enum {value = 1}; };

Standartizuojant C ++ 98 buvo įvesta klasinių statinių konstantų iniciatorių sąvoka, kad „Pow3“ metaprograma galėtų atrodyti taip:

meta / pow3const.hpp // pirminis šablonas 3 skaičiavimui į N-ąjį šabloną struct Pow3 {static int const value = 3 * Pow3 :: value; }; // visa specializacija baigti rekursijos šabloną struct Pow3 {static int const value = 1; };

Tačiau šioje versijoje yra trūkumas: statiniai pastovieji nariai yra vertės. Taigi, jei turite tokią deklaraciją kaip

negaliojantis foo (int const &);

ir jūs perduosite metaprogramos rezultatą:

foo (Pow3 :: vertė);

kompiliatorius turi praeiti adresas apie Pow3 :: vertė, ir tai priverčia kompiliatorių instancijuoti ir paskirstyti statinio nario apibrėžimą. Todėl skaičiavimas nebėra vien tik „kompiliavimo laiko“ efektas.

Surašymo reikšmės nėra vertės (t. Y. Jos neturi adreso). Taigi, kai perduodate juos pagal nuorodą, statinė atmintis nenaudojama. Beveik lygiai taip pat, tarsi apskaičiuotą vertę perteiktumėte kaip pažodinį žodį.

Tačiau įvestas C ++ 11 constexpr statinių duomenų nariai, ir jie neapsiriboja vientisais tipais. Jie neišsprendžia anksčiau iškelto adreso klausimo, tačiau, nepaisant to, kad dabar jie yra įprastas metaprogramų rezultatų rengimo būdas. Jie turi pranašumą, kad turi teisingą tipą (priešingai nei dirbtinis enum tipas), ir tą tipą galima išskaičiuoti, kai statinis narys yra deklaruojamas su automatinio tipo specifikatoriumi. C ++ 17 pridėjo tiesioginių statinių duomenų narius, kurie išsprendžia aukščiau iškeltą adreso problemą ir gali būti naudojami su constexpr.

Metaprogramavimo istorija

Ankstyviausią dokumentuotą metaprogramos pavyzdį pateikė Erwinas Unruhas, tada atstovavęs „Siemens“ C ++ standartizacijos komitete. Jis atkreipė dėmesį į skaičiuojamąjį šablono egzemplioriaus proceso užbaigtumą ir pademonstravo savo mintį sukurdamas pirmąją metaprogramą. Jis panaudojo „Metaware“ kompiliatorių ir ragino jį išleisti klaidų pranešimus, kuriuose būtų nuoseklūs pirminiai skaičiai. Štai kodas, kuris buvo išplatintas 1994 m. C ++ komiteto posėdyje (modifikuotas taip, kad dabar jis sudaromas pagal standartus atitinkančius kompiliatorius):

meta / unruh.cpp // pirminio skaičiaus apskaičiavimas // (pakeistas gavus originalo leidimą nuo 1994 m. Erwino Unruho) šablonas struct is_prime {enum ((p% i) && is_prime2? p: 0), i-1> :: pri); }; šablonas struct is_prime {enum {pri = 1}; }; šablonas struct is_prime {enum {pri = 1}; }; šabloną struct D {D (negaliojantis *); }; šabloną struct CondNull {static int const reikšmė = i; }; template struct CondNull {static void * reikšmė; }; negaliojantis * CondNull :: reikšmė = 0; šabloną struct Prime_print {

// pagrindinis šablonas kilpai spausdinti pirminius skaičius Prime_print a; enum {pri = is_prime :: pri}; negaliojantis f () {D d = CondNull :: reikšmė;

// 1 yra klaida, 0 yra gerai a.f (); }}; šablonas struct Prime_print {

// visa specializacija baigti ciklo enum {pri = 0}; tuštuma f () {D d = 0; }; }; #ifndef LAST #define LAST 18 #endif int main () {Prime_print a; a.f (); }

Jei sukursite šią programą, kompiliatorius atspausdins klaidos pranešimus, kai, Pirminis atspaudas :: f (), inicializuoti d nepavyksta. Tai atsitinka, kai pradinė vertė yra 1, nes yra tik void * konstruktorius, o tik 0 teisingai konvertuojamas į tuštuma*. Pvz., Viename kompiliatoriuje (tarp kelių kitų pranešimų) gauname šias klaidas:

unruh.cpp: 39: 14: klaida: nėra perspektyvaus konversijos iš „const int“ į „D“ unruh.cpp: 39: 14: klaida: nėra perspektyvaus konversijos iš „const int“ į „D“ unruh.cpp: 39: 14: klaida: nėra perspektyvaus konversijos iš „const int“ į „D“ unruh.cpp: 39: 14: klaida: nėra perspektyvaus konversijos iš „const int“ į „D“ unruh.cpp: 39: 14: klaida: nėra perspektyvu konversija iš „const int“ į „D“ unruh.cpp: 39: 14: klaida: nėra perspektyvaus konversijos iš „const int“ į „D“ unruh.cpp: 39: 14: klaida: nėra perspektyvaus konversijos iš „const int“ į „D“

Pastaba: Kadangi klaidų tvarkymas kompiliatoriuose skiriasi, kai kurie kompiliatoriai gali nustoti veikti atsispausdinę pirmąjį klaidos pranešimą.

C ++ šablonų metaprogramavimo, kaip rimto programavimo įrankio, koncepciją pirmasis išpopuliarino (ir šiek tiek įformino) Toddas Veldhuizenas savo darbe „C ++ šablonų metaprogramų naudojimas“. Veldhuizeno darbas „Blitz ++“ (skaitinių masyvų biblioteka C ++) taip pat įvedė daug patobulinimų ir išplėtimų metaprogramavimui (ir išraiškos šablonų technikai).

Tiek pirmasis šios knygos leidimas, tiek Andrejaus Alexandrescu Šiuolaikinis „C ++“ dizainas prisidėjo prie C ++ bibliotekų sprogimo, panaudojant šablonais pagrįstą metaprogramavimą, sukataloguojant kai kurias pagrindines technikas, kurios vis dar naudojamos. „Boost“ projektas padėjo sutvarkyti šį sprogimą. Anksti ji pristatė MPL (metaprogramavimo biblioteką), kurioje apibrėžta nuosekli sistema tipo metaprogramavimas taip pat išpopuliarėjo per Davido Abrahamo ir Aleksejaus Gurtovoy knygą C ++ šablonų metaprogramavimas.

Louisas Dionne'as padarė papildomų svarbių pasiekimų, kad metaprogramavimas būtų sintaksiniu požiūriu labiau prieinamas, ypač per „Boost.Hana“ biblioteką. Dionne, kartu su Andrewu Suttonu, Herbu Sutteriu, Davidu Vandevoorde'u ir kt., Dabar standartizacijos komitete vadovauja pastangoms suteikti aukščiausios klasės metaprogramavimo paramą šia kalba. Svarbus to darbo pagrindas yra tirti, kokios programos savybės turėtų būti prieinamos apmąstant; Matúš Chochlík, Axelis Naumannas ir Davidas Sankelis yra pagrindiniai šios srities bendradarbiai.

Johnas J. Bartonas ir Lee R. Nackmanas iliustravo, kaip sekti matmenų vienetus atliekant skaičiavimus. „SIunits“ biblioteka buvo išsamesnė biblioteka, skirta fiziniams vienetams tvarkyti, kurią sukūrė Walteris Brownas. std :: chrono komponentas standartinėje bibliotekoje susijęs tik su laiku ir datomis, prie jo prisidėjo Howardas Hinnantas.