Sűrűn előforduló esemény, hogy C++-ban egy lista elemein kell végiglépkedni egy ciklusban. A for_each egy jó megoldás lenne, de ahhoz mindig egy függvényt kell definiálni, amit a for_each majd jól meghív. C++-ben viszont nincs anonymous függvény, mint Perl-ben, vagy nem lehet függvényen belül függvényt definiálni, mint Pascal-ban. Egy class-on belüli class definíció még lehetséges lenne, ami implementálja a () operátort, de az csúnyán megtördeli a kódot.
A C++0x kiterjesztésben találtam erre nézvést törekvéseket. Visual Studio-ban vannak C++0x-es megoldások. Rá is leletem a decltype() függvényre, amivel fordítási időben vissza tudjuk kapni az adat típusát. Nézzük meg, hogy mit lehet ezzel kezdeni?
Szóval a probléma:
list<CType> List;
//...
for(list<CType>::iterator iter = List.begin();
iter != List.end(); ++iter )
{
//...
}
Hogyan lehet ezt egyszerűsíteni? Az lenne a legjobb, ha csak a List-et kellene megadnom és abból kitalálná, hogy milyen iterátor-t kell használni. Valami makróban gondolkodtam, hogy ne kelljen függvényt átadni paraméterként.
Az első lehetőség ez lenne:
#define FOREACH(_Iter, _Type, _Container) \
for(list<_Type>::iterator _Iter = _Container.begin(); \
_Iter != _Container.end(); ++_Iter)
De itt "feleslegesen" meg kell adni a List elemeinek típusát, amit a fordító már ismer. További gond, hogy minden konténer osztályra külön kell egy ilyet definiálni. Na, pont erre van a decltype() függvény.
#define FOREACH(_Iter, _Container) \
for(decltype(_Container.begin()) _Iter = _Container.begin(); \
_Iter != _Container.end(); ++_Iter )
Egy még régebbi g++-szal (4.1.2) próbálkoztam és nem működött. Ott a decltype helyett még __typeof__ ment.
#define FOREACH(_Iter, _Container) \
for(__typeof__(_Container.begin()) _Iter = _Container.begin(); \
_Iter != _Container.end(); ++_Iter )
Ez már szépen megoldja mind a két problematikát! Pár perces keresgélés után találtam még egy érdekességet. Ez az auto "típus", amit VS 2010-ben az MS szintén átvett a C++0x-ből. Az auto típust pontosan ugyanúgy használhatjuk, mint bármelyik egyszerű, vagy összetett típust. Az értékadás eredményének típusa lesz a deklarált változó típusa. Tehát a
#define FOREACH(_Iter, _Container) \
for(auto _Iter = _Container.begin(); _Iter != _Container.end();
++_Iter )
pont azt csinálja, amit szeretnék. Használata:
list<CType> List;
//...
FOREACH(iter, List)
{
//... az iter nevű iterátort lehet használni
}
Mondjuk ettől a kódunk nem biztos, hogy hordozható lesz. Bár én azt látom, hogy a 4.x-es gcc már tudja a C++0x dolgokat (egy példa) (x valszeg > 1). Nekem VS2010 alatt nem ment a decltype(List)::iterator, de az előző cikk szerint a gcc 4.6 óta már tudja.
Ebből csinálhatunk olyan ciklust is, amit const_terator-t használ. Ehhez egy kicsit még trükközni kell, mert a régi C++-ban nincs cbegin(), amit egyből a megfelelő típusú iterator-t ad vissza:
template <class T>
struct IterType { typedef typename T::const_iterator citer_t; };
#define FOREACHCONST(_CIter, _Container) \
for(IterType<typeof(_Container)>::citer_t _CIter = _Container.begin(); \
_CIter != _Container.end(); ++_CIter )
A C++0x szerint még egyszerűbben is megoldhatjuk a fenti dolgot (Range-based for-loop) minden olyan esetben, amikor a tároló objektum támogatja az iterátorokat. Sajna ezt a VS2010 nem támogatja, pedig milyen szép is ez!
int my_array[5] = {1, 2, 3, 4, 5};
for (int &x: my_array) { x *= 2;}
Hoppácska! A C++0x már tud lambda függvényeket (l. perl anomymous functions)! Tud ilyen a VS2010? Úgy látom, hogy igen! Leírás >itt<. Ezt a lambda függvényt megadhatjuk a for_each harmadik paramétereként! Nattyon kúl!
Iteráljunk minden nap!
+jegyzések