A programozás már önmagában is elég jól tornáztatja az agyat, de ezt tovább lehet fokozni, ha valaki funkcionális programozásra adja a fejét.
A legjobb eszköz erre a Prolog, de C++-ban is lehet hasonlóan érdekes feladatokat megoldani a template metaprogramming segítségével.
Mentolos cukorka
A sablonos cikkben írtam egy számokat kezelő metalistát, ebből fogok kiindulni. A különbség annyi, hogy enum helyett typedef-ként nevesítem
a metaváltozókat:
CODE
template <typename _head, typename _tail>
struct typelist
{
typedef _head head;
typedef _tail tail;
};
template <typename _head>
struct typelist<_head, void>
{
typedef _head head;
typedef void tail;
};
Hasonlóan lehet megadni a listát, mint a számok esetében, pl. typelist<int, typelist<float, typelist<double, void> > >. Ezt így hosszú leírni, ezért néhány makróval könnyítem meg a dolgot: CODE
#define TL1(a) typelist<a, void>
#define TL2(a, b) typelist<a, typelist<b, void> >
#define TL3(a, b, c) typelist<a, typelist<b, typelist<c, void> > >
A könnyű kezeléshez két darab metafüggvényt kell implementálni: a lista i-edik elemének elérése és a lista hossza. Ily módon lehet majd iterálni a listán. Ezeknek a megvalósítása rekurzív sablonokkal történik: CODE
template <typename ml, int index>
struct type_at
{
typedef typename type_at<typename ml::tail, index - 1>::value value;
};
template <typename ml>
struct type_at<ml, 0>
{
typedef typename ml::head value;
};
Feltűnhet, hogy sok helyen kell a typename kulcsszó, ugyanis a legtöbb név függ valamilyen sablon paramétertől és minősített. A baj az, hogy a fordítási hibákból elég nehéz kikövetkeztetni, hogy hova kéne typename. CODE
template <typename ml>
struct length
{
enum { value = length<typename ml::tail>::value + 1 };
};
template <>
struct length<void>
{
enum { value = 0 };
};
Höfö append és erase metafüggvényt írni (ne felejtsük el, hogy a TMP Turing teljes, tehát ezek nem okozhatnak problémát).
Variálós sablon
Nem nehéz kitalálni, hogy egy variadic template paramétere egy ilyen típuslista lesz. A kérdés inkább az, hogy hogyan dolgozzuk fel a listaelemeket?
Egy olyan listát kéne csinálni, aminek többféle push_back metódusa van, méghozzá olyanok, amik a típuslista egy adott elemét fogadják be.
CODE
template <typename type_list>
class mtlist : public _mtlist<type_list, length<type_list>::value - 1>
{
};
Ebben az iterálós sablonban pedig lépésenként adjunk hozzá egy új metódust a már létezőkhöz. Fontos, hogy az ősosztály metódusait láthatóvá tegyük a using kulcsszóval, különben csak az utolsó lesz érvényes. CODE
template <typename type_list, int index>
class _mtlist : public _mtlist<type_list, index - 1>
{
typedef typename type_at<type_list, index>::value value_type;
public:
using _mtlist<type_list, index - 1>::push_back;
void push_back(const value_type& value);
};
A rekurziót a 0-s jelzésű specializált sablon fogja megállítani: CODE
template <typename type_list>
class _mtlist<type_list, 0>
{
typedef typename type_at<type_list, 0>::value value_type;
public:
virtual ~_mtlist();
void push_back(const value_type& value);
};
Szemfüles olvasók észrevehetik, hogy a typedefeket priváttá tettem, így mindegyik osztályban tudom használni ugyanazt a nevet, nem lesz ütközés. Az érdekes dolgok most kezdődnek; hogyan lehetne implementálni a push_back metódusokat? Lehet, hogy ez egy létező tervminta, mert sok helyen lehet alkalmazni: van egy absztrakt struktúra, legyen ez metalink_base, és ebből származtatok le konkrét, sablonos linkeket. Mindezt a legősibb (azaz a 0-s) multilistben kell megcsinálni, és ez fogja tárolni a fejelemet is. CODE
protected:
struct metalink_base
{
virtual ~metalink_base() {}
metalink_base* next;
metalink_base* prev;
};
template <typename value_type>
struct metalink : metalink_base
{
value_type value;
};
metalink_base* head;
Egy érdekes észrevétel, hogy a sima listával szemben a fejelem itt nem foglal extra memóriát. Ugyanúgy a konstruktorban kell létrehozni. A metódusok implementációja innentől már magától értetődő, úgyhogy nem is írom le.
Iterátor
Csak a rend kedvéért egy iterátort is írtam az osztályhoz, ezt viszont már a legfiatalabb osztályba kell rakni. Ami érdekes ebben, hogy
az operator * metódusa is sablon függvény kell legyen. Semmi akadálya, de én inkább egy get metódust írok, mert
könnyebb leírni (megj.: ugyanis it.operator *<típus>()-t kéne egyébként, hacsak a fordító ki nem következteti a paramétert).
CODE
template <typename deref_type>
deref_type& get()
{
metalink<deref_type>* cp = dynamic_cast<metalink<deref_type>*>(ptr);
if( !cp )
throw 1;
return cp->value;
}
A típusbiztosságot a dynamic_cast garanáltja, tehát ha rossz típussal akarod dereferálni az iterátort, akkor vidám exceptiont kapsz. Ez nem mindig vicces, ezért csináltam még egy get_ptr metódust is (ez lenne az operator ->), ami viszont 0-t ad vissza, ha rossz a típus.
Summarum
Nézzük meg hogy működik ez:
CODE
typedef mtlist<TL3(int, float, Apple)> meta3_ifA;
meta3_ifA l1;
Apple a;
a.name = "apple";
l1.push_back(a);
l1.push_back(5);
l1.push_back(3.5f);
l1.push_back(2);
l1.push_back(0.1f);
int cnt = 0;
for( meta3_ifA::iterator it = l1.begin(); it != l1.end(); ++it )
{
if( cnt == 0 )
std::cout << it.get<Apple>().name << "\n";
if( cnt == 1 || cnt == 3 )
std::cout << it.get<int>() << "\n";
else if( cnt == 2 || cnt == 4 )
std::cout << it.get<float>() << "\n";
++cnt;
}
Látszólag semmi haszna nincs, hiszen elemenként le kell kezelni mindent. A pointeres metódus használatával viszont nem muszáj, például végrehajthatsz valamit csak Apple típusú elemekre. Mivel más nyelvekben nyelvi szinten van ilyen lista (pl. Objective-C), talán C++-ban is lehetne valamire használni. Hasonlóan őrült implementációk találhatóak a boost C++ libraryban. Kód itt. Höfö:
Irodalomjegyzék http://en.cppreference.com/w/cpp http://www.cplusplus.com/ http://www.parashift.com/c++-faq-lite/ (ezt különösen ajánlott elolvasni) http://aszt.inf.elte.hu/~gsd/halado_cpp/ (ezt is) |