Bind zastępuje predykaty!

Boost.bind to potężne narzędzie, za niedługo zobaczymy je w standardzie, obecnie dostępne w TR1.

Dzięki niemu możemy pozbyć się wielu prostych i trywialnych, a także tych trochę bardziej skomplikowanych funktorów, jakie musieliśmy definiować, bo standardowe adaptory funkcyjne z STL są ograniczone.

To przez niego z kodu źródłowego xime zniknie kilka predykatów, które zostaną zastąpione jedno-dwu linijkowymi złożeniami bindowych adapterów i obiektów funkcyjnych. Przez to kod stanie się czytelniejszy i bardziej spójny, bo nawet prosty predykat wykorzystujący jakiś element lub metodę obiektu definiowany jest jako klasa funktora, co przy kilku dodatkowych definicjach klas powoduje rozrastanie się kodu.

Może mały, ciekawy przykład z życia wzięty, może nie do końca zastępujący klasę funktora predykatu, ale ukazujący implementację przydatnego uogólnionego algorytmu z wykorzystaniem kilku mozliwości boost-a.

Tak się składa, że w xime i jego API niektóre obiekty będą identyfikowane po zawartości pola ID, przy czym dla rożnych typów obiektów, struktur typ tego elementu będzie inny. Dla jednych będzie to uchwyt w postaci wskaźnika, dla innych int, a niektóre będą identyfikowane po ciągu znakowym.

Przydałaby się funkcja do przeszukiwania podanego zakresu, tablic, czy kontenerów takich obiektów właśnie po tym polu ID.

Jak to zrobić, aby się nie narobić, czyli nie napisać od cholery kodu, dla każdego typu struktur?

Prosto, z pomocą przychodzi nam Boost.bind i Boost.Typeof.

W rzeczy samej funkcja taka jest prostą "nakładką" na standardowy find_if:

template<typename Iter, typename Id>
inline Iter find_by_id(Iter first, Iter last, Id id) {
 
	typedef typename std::iterator_traits<Iter>::value_type value_type;
	typedef BOOST_TYPEOF_TPL(first->ID) ID_type;
 
	return std::find_if(first, last,
		boost::bind(std::equal_to<ID_type>(), boost::bind(&value_type::ID, _1), id));
 
}

Domyślam się, że samego predykatu nie trzeba tłumaczyć, jedynie dwie linijki definiujące nazwy typów mogą być zastanawiające.

Typ obiektu po jakim iterujemy jest nam potrzebny do określenia odpowiedniego wskaźnika do składowej ID danego typu. Oczywiście moglibyśmy wykorzystać typ stowarzyszony iteratora value_type, ale niestety wtedy nasz algorytm nie będzie współpracował z wskaźnikami podstawionymi w roli iteratorów, nawet się nie skompiluje.

Na to rozwiązaniem jest klasa cech iterator_traits, która definiuje podstawowe typy dla iteratora. Dla zwykłych iteratorów przepisuje ich typy stowarzyszone, a odpowiednia specjalizacja dla wskaźników definiuje odpowiednie typy na podstawie jego typu. Także bezproblemowo możemy pobrać potrzebne nam informacje.

Funktor equal_to musi być stworzony z konkretyzacji typu jaki będzie porównywał, czyli typ elementy ID danego obiektu, na jakich operuje funkcja. Do poznania typu tego elementu służy makro BOOST_TYPEOF, które na podstawie zmiennej wyznacza jej typ.

Odwołanie się do elementu ID obiektu wskazywanego przez first może okazać się niebezpieczne w sytuacji kiedy first będzie równy last, czyli wskazywanie za ostatni element, ale nie ma się czego obawiać. Program nie będzie próbował operować na tych danych, zmienna ta jest jedynie potrzebna w czasie kompilacji do "pobrania" jej typu.

Nasza funkcja wykorzystuje funktor equal_to z biblioteki standardowej, który standardowo używa operatora porównania == do wyznaczenia równości obiektów, co dla większości typów jest jak najbardziej zalecane i da pożądany efekt.

Dla ciągów znakowych musimy dodać jeszcze odpowiednia specjalizację tego szablonu, aby porównywanie ciągów dało spodziewany efekt, a nie tylko porównywanie wskaźników:

namespace std {
 
template<>
struct equal_to<char*> : public binary_function<char*, char*, bool> {
 
	bool operator()(const char* left, const char* right) const {
		return strcmp(left, right) == 0;
	}
 
};
 
} // namespace std

Wypadałoby powyższą implementacje wzbogacić o "obsługę" pustych wskaźników.

Dla typów definiowanych przez użytkownika, najlepiej będzie zdefiniować operator porównania, albo wyspecjalizować wersję funktora equal_to to dla swojego typu.

Na koniec tym razem nie będzie żadnego przykładowo-testowego kodu, nie ma zbytnio co tutaj testować, jeśli uda się skompilować to musi działać. Jak ktoś usilnie nalega to sobie sam przetestuje ;p

A w kodzie wynikowym kompilator wygeneruje jedną pętelkę z jednym ifem ;)

Zapewne funkcja ta wyląduje w API xime, może wśród innych, podobnych i przydatnych algorytmów, jakie będą wykorzystywane przez sam program.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *