Ugrás a tartalomhoz

Template-metaprogramozás

Ellenőrzött
A Wikipédiából, a szabad enciklopédiából
(Template metaprogramozás szócikkből átirányítva)

A template-metaprogramozás a metaprogramozás egyik módszere. Lényege, hogy a fordító sablonokból generál ideiglenes kódot, amit összeszerkeszt a forrással, és ebből készíti a programot. A kimenetek fordítási idejűek, konstansok, adatszerkezetek és függvények. Úgy lehet rá gondolni, mint a sablonok fordításidejű végrehajtására. Több nyelven is használható, a legismertebb a C++, de lehet template metaprogramozni D, Curl és XL nyelveken is.

A template-metaprogramozást véletlenül fedezték fel.[1]

Néhány más nyelv támogat hasonló, vagy akár még többre képes technikákat, például Lisp makrók. Ezek azonban nem tárgyai a jelen cikknek.

Elemei

[szerkesztés]

A sablonok használata alapvetően két műveletet foglal magába: a sablon definiálását és példányosítását. A definíció leírja a generált kód általános formáját, és a példányosítás specifikus kódot hoz belőle létre. A template metaprogramozás Turing-teljes, ami azt jelenti, hogy minden megoldható benne, amit csak programmal meg lehet oldani.[2]

A sablonok különböznek a makróktól. A makró, ami szintén fordítás idejű, szövegmanipulációval és helyettesítéssel generál kódot. A makrórendszereknek általában korlátozott fordításidejű adatfeldolgozási képességgel bírnak, és nem veszik figyelembe a nyelv szemantikáját és típusrendszerét. A Lisp makrók ettől eltérően működnek, Lispben íródnak, és a feldolgozott szöveget Lisp adatszerkezetekként kezelik.

A template metaprogramozás funkcionális programnyelv, nincsenek benne változók, csak ismeretlenek, amelyek értéke egyszer számítható ki. Sok implementáció csak rekurzióval teszi elérhetővé az adatokat, amire majd példát is adunk.

Habár szintaxisában nagyban eltér attól a nyelvtől, amihez tartozik, gyakorlati haszna van. A sablonok rendszerint a generikus programozás eszközei, hogy ne kelljen kis változtatásokkal lényegében ugyanazt megírni. Egy másik ok a fordítás idejű optimalizálás, ami valamit egyszer megcsinál, ahelyett, hogy a program futás közben többször megcsinálná. A fordító kigönyölíti a ciklusokat, ezzel elkerülve ugrásokat és a ciklusváltozó használatát.

Fordítás idejű osztálygenerálás

[szerkesztés]

A fordítás idejű programozást egy faktoriálist számító példával szemléltetjük:

unsigned int factorial(unsigned int n) {
	return n == 0 ? 1 : n * factorial(n - 1); 
}

//  Használata:
// factorial(0) eredménye 1;
// factorial(4) eredménye 24.

Ez a kód futás időben hívódik meg, és számítja ki a faktoriálist. Ha template metaprogramozással és specializációval határozzuk meg a rekurzió végfeltételét, a ténylegesen használt faktoriálisok számítása áttevődhet fordítási időbe:

template <unsigned int n>
struct factorial {
	enum { value = n * factorial<n - 1>::value };
};

template <>
struct factorial<0> {
	enum { value = 1 };
};

// Felhasználása:
// factorial<0>::value értéke 1;
// factorial<4>::value értéke 24.

A fenti példa a 4 és a 0 faktoriális értékét fordítási időben számítja ki, és az eredményeket előre kiszámolt konstansokként használja tovább. Ehhez a paramétereknek fordítási időben ismertnek kell lenniük. Tehát a paraméternek konstans literálnak vagy kifejezésnek kell lennie.

A C++11 bővítései közé tartozik a constexpr, ami arra utasítja a fordítót, hogy egyszerű konstans kifejezéseket hajtson végre. Ez lehetővé teszi, hogy a szokásos függvénydefiníciót használjuk.[3]

Fordítás idejű kódoptimalizáció

[szerkesztés]

A fenti faktoriális példa a fordítás idejű optimalizációra is jó, mivel az előzetesen kiszámolt konstansokat már nem kell futás időben kiszámolni, amivel csökken a program memóriaigénye és gyorsabbá is válik. Ez azonban csak egy kis mértékű optimalizáció.

Másik fontos példa a ciklusok kigöngyölítése. A template metaprogramozással létrehozhatók n hosszú vektor osztályok, ha n fordítás időben ismert. A hagyományos n hosszú vektorokkal szemben ez megengedi a ciklusok kigönygyölítését. Példaként tekintsük az összeadást. Az n hosszú vektorok összeadása írható, mint:

template <int length>
Vector<length>& Vector<length>::operator+=(const Vector<length>& rhs) 
{
    for (int i = 0; i < length; ++i)
        value[i] += rhs.value[i];
    return *this;
}

Példányosításkor a következő kód jöhet létre:

template <>
Vector<2>& Vector<2>::operator+=(const Vector<2>& rhs) 
{
    value[0] += rhs.value[0];
    value[1] += rhs.value[1];
    return *this;
}

A fordító ki tudja göngyölíteni a ciklust, mivel a length paraméter fordítás időben konstans. Ezzel azonban óvatosnak kell lenni, mert felduzzaszthatja a kódot, ha sok ilyen ciklus van.

Statikus polimorfizmus

[szerkesztés]

A polimorfizmus egy szabványos programozási lehetőség, amiben a származtatott osztályok példányai használhatók a bázisosztály objektumaiként, de a leszármazott osztály metódusai hívódnak meg.

class Base
{
public:
    virtual void method() { std::cout << "Base"; }
    virtual ~Base() {}
};

class Derived : public Base
{
public:
    virtual void method() { std::cout << "Derived"; }
};

int main()
{
    Base *pBase = new Derived;
    pBase->method(); //outputs "Derived"
    delete pBase;
    return 0;
}

ahol a virtuális metódusok minden hívása a legspeciálisabb osztály metódusát hívja. Ez a dinamikus polimorfizmus a virtuális táblákkal van megvalósítva. Futásidőben ez alapján dől el, hogy melyik metódus hívódik meg. Ez a futás idejű polimorfizmus szükségképpen lassítja a programot, habár modern processzorokban ez kismértékű. Sok esetben azonban már fordítás időben el lehetne ezt dönteni. A Curiously Recurring Template Pattern (CRTP) használatával statikus polimorfizmust érhetünk el, ami imitálja a polimorfizmust, de már fordítás időben hozzáférhető, így nem kell a virtuális táblákban keresgélni. Például:

template <class Derived>
struct base
{
    void interface()
    {
         // ...
         static_cast<Derived*>(this)->implementation();
         // ...
    }
};

struct derived : base<derived>
{
     void implementation()
     {
         // ...
     }
};

Itt a bázisosztály élvezi annak az előnyét, hogy a tagfüggvények törzse nem példányosul a deklarációig, és a leszármazott osztályok tagjait használja tagfüggvényeiben a static_cast használata miatt, így fordításkor polimorf jellemzőkkel bíró objektum-összetétel jön létre. Például a Boost iterator könyvtára CRTP-t használ.[4]

Hasonló használat a Barton–Nackman-trükk, amire néha korlátozott sablon kiterjesztésként is hivatkoznak. A közös funkcionalitást a bázisosztályba helyezik, amit nem szerződésként, hanem szükséges komponensként használnak a megfelelő viselkedéshez, a kód redundanciájának csökkentésével.

Előnyök és hátrányok

[szerkesztés]

Fordítás idejű és futás idejű kapcsolat: A template metaprogramozás lelassítja a fordítást. A C++ szabvány tartalmazza, hogy milyen körülmények között mely sablonok példányosulnak implicit módon. A sablondefiníciók nem feltétlenül példányosulnak, és az osztály definíciója nem jelenti azt, hogy az összes tag példányosul. Végeredményként a felhasználástól függ, hogy valóban lassabb lesz a fordítás.

Generikus programozás: a template metaprogramozás lehetővé teszi, hogy a programozó inkább a szerkezettel foglalkozzon, a kód generálását pedig a fordítóra hagyja a kliens kódtól függően. Így a template metaprogramozás valóban generikus kód írását teszi lehetővé, amivel rövidülhet a kód és javulhat a karbantarthatóság.

Olvashatóság: Ami a C++-t illeti, a template metaprogramozás szintaxisa és idiómái annyira különböznek a normál C++ kódtól, hogy nehéz őket megérteni.[5][6]

Fordítás

[szerkesztés]

Ez a szócikk részben vagy egészben a Template metaprogramming című angol Wikipédia-szócikk fordításán alapul. Az eredeti cikk szerkesztőit annak laptörténete sorolja fel. Ez a jelzés csupán a megfogalmazás eredetét és a szerzői jogokat jelzi, nem szolgál a cikkben szereplő információk forrásmegjelöléseként.

Jegyzetek

[szerkesztés]
  1. See History of TMP on Wikibooks
  2. C++ Templates are Turing Complete
  3. http://www.cprogramming.com/c++11/c++11-compile-time-processing-with-constexpr.html
  4. http://www.boost.org/libs/iterator/doc/iterator_facade.html
  5. (2004) „DSL implementation in metaocaml, template haskell, and C++”, Kiadó: University of Waterloo, University of Glasgow, Research Centre Julich, Rice University. [2016. március 5-i dátummal az eredetiből archiválva]. (Hozzáférés: 2017. május 5.) „C++ Template Metaprogramming suffers from a number of limitations, including portability problems due to compiler limitations (although this has significantly improved in the last few years), lack of debugging support or IO during template instantiation, long compilation times, long and incomprehensible errors, poor readability of the code, and poor error reporting. 
  6. (2002) „Template Meta-programming for Haskell”, Kiadó: ACM 1-58113-415-0/01/0009. „Robinson’s provocative paper identifies C++ templates as a major, albeit accidental, success of the C++ language design. Despite the extremely baroque nature of template meta-programming, templates are used in fascinating ways that extend beyond the wildest dreams of the language designers. Perhaps surprisingly, in view of the fact that templates are functional programs, functional programmers have been slow to capitalize on C++’s success