петък, 23 май 2008 г.

<T>emplate magic

Просто не мога да се сдържа да не се похваля какво извращение успях да подкарам на C++ наскоро. Става дума за тип, в който може да се слага всеки друг тип (по един във всеки момент) и да се взима текущия тип (като се каже какъв е). Т.е исках да върви подобен код:


generic x;
x.set (5);
x.get <int> ();//--> 5
x.set (std::string ("alabala"))
x.get <std::string> ();//--> alabala

//type checking
x.is <int> ();//false


Та значи горното нещо в началото ми се струваше сън, докато не осъзнах, че е напълно реално - нужни са само 2 помощни класа (т.е общо 3) и горният код РАБОТИ.

Накратко:
1. Абстрактен базов клас, поддържащ методи set и get, приемащи/връщащи void *
2. темплейтен наследник на горния клас, в който се пази указател към T, но самите функции разбира се връщат пак void * (защото са в йерархия)
3. клас wrapper на горния, който не е темплейтен, но съдържа темплейтнати функции (т.е класа не е темплейт, но някой негови функции са), който държи един указател към базата и когато поискат нещо от него той прави dynamic_cast до наследник от искания тип, след което каства резултата към нещо човечко (т.е void * --> Т * --> Т). Хубавото е, че примерно set функцията сама разбира типа на шаблона (дедуцира го от аргумента) - затова няма смисъл да го пишем изрично (т.е set <int> (4). За get функцията обаче е задължителен (е няма пълно щастие :) ).

Хубавото тука е, че се извършва type checking от компилатора (при dynamic_cast), както и освобождаването на заделената памет от виртуалния деструктор на 2рия клас - т.е общо взето прехвърляме задълженията си към компилатора (както и според мен трябва да се прави).

п.с.: За да е малко по-полезно това чудо добавих и функция, която преобразува всеки тип до стринг (за да мога да печатам по лесно наред). Та наложи се да сложа и темплейт-специализация на тази вункция за типа int (т.е функцията to_str за едни типове да работи по един начин, за int да работи по друг). Тази функция, се третира като нормална, и за това трябва да стой в cpp файл, който се линква отделно (т.е не в h/hpp файла със шаблона, защото ако се инклудне от поне 2 места става мазало). По принцип темплейтите трябва да са написани в един h файл, защото компилатора не позволява да ги разделяме (extern template не е имплементирано почти никъде), но точно специализациите може (и трябва) да се пишат в отделен файл - иначе казва redefinition of ...


#include <cstdio>
#include <string>

class base {
public:
virtual void *get () = 0;
virtual void set (const void *) = 0;
virtual ~base () {}
};

template <typename T>
class derived : public base {
T *_data;
public:
derived () : _data (NULL) {}
virtual void *get () {
return static_cast <void *> (_data);
}
virtual void set (const void *data) {
delete _data;
_data = new T (*static_cast <const T *> (data));//copy constructor
}
virtual ~derived () {
delete _data;
}
};

class wrapper {
base *_p;
public:
wrapper () : _p (NULL) {}
template <typename T> T get () {
return *static_cast <T *> (dynamic_cast <derived <T> *> (_p)->get ());
}
template <typename T> bool is () {
return dynamic_cast <derived <T> *> (_p) != NULL;
}
template <typename T> void set (const T &nv) {
delete _p;
_p = new derived <T>;
_p->set (&nv);
}

};

int main () {

wrapper x;
x.set (5);
printf ("%d\n", x.get <int> ());

x.set (std::string ("alaabala"));
printf ("%s\n", x.get <std::string> ().c_str ());
printf ("%d %d\n", (int)(x.is <int> ()), (int)(x.is <std::string> ()));

return 0;
}