Problem z STL -> for_each, mem_fun

0

Witam!

Na wstępię zaznaczę, że mam pewne istotne braki z filozofii STLa i typów generycznych w C++.

Chciałbym podać referencję do "member function", która później byłaby użyta w for_each.

Kod prezentujący mój problem:

#include <functional>
#include <vector>
#include <algorithm>
#include <iostream>


class container {
	std::vector<int> vect; // [ 0, 1, 2, 3, 4 ]

	public:
		container() {
			for (int i = 0; i < 5; i++ )
				vect.push_back(i);
		}

		template<class Function>
			void each (Function f) {
				for_each(vect.begin(), vect.end(), f);
			}
} o_container ;

class caller {
	public:
		void funkcja(int i) { std::cout << i; }
		caller() {
			o_container.each ( std::mem_fun(funkcja) ); // Ten kod generuje błąd-> argument of type 'void (caller::)(int)' does not match 'void (caller::*)(int)' pod MinGW
		}
} o_caller;

int main() {

	return 0;
}

 

Ja już tego po prostu nie ogarniam, a google i inne szukaji nie pomagają ;(

Z góry dziękuję za poświęcenie chwilki czasu!

0

Na pierwszy rzut oka: potrzebujesz adresu do member function, dlatego też w wywołaniu std::mem_fun() powinieneś podać pełną specyfikację, łącznie z klasą:

std::mem_fun(&caller::funkcja)

Edit Patrząc na komunikat błędu:

argument of type void (caller::)(int) does not match void (caller::*)(int)

można wywnioskować, że zadziałałoby nawet bez specyfikacji typu klasy, brakuje jedynie operatora adresu &. Ale to domysły. Poczytaj więcej tutaj, szczególnie example: http://www.cplusplus.com/reference/std/functional/mem_fun/

0

Na drugi rzut oka "funkcja" ma de facto dwa argumenty (caller *, int), więc należałoby to jakoś uwzględnić.
Albo użyć "static" - co pewnie nie wchodzi w rachubę, albo pokombinować...

Przykład ze strony boost:

boost::function<int (int)> f;
X x;
f = std::bind1st(
      std::mem_fun(&X::foo), &x);
f(5); // Call x.foo(5)

http://www.boost.org/doc/libs/1_46_1/doc/html/function/tutorial.html

0

OK, dzięki, vpiotr. Rzeczywiście, mem_fun() zwraca funkcję dwuargumentową, gdzie pierwszym argumentem jest adres obiektu, na rzecz którego wywołana będzie metoda klasy. Dlatego należy zmienić wywołanie o_container.each() w taki sposób:

caller() {
	o_container.each(std::bind1st(std::mem_fun(&caller::funkcja), this));
}

W ten sposób bindujemy pierwszy argument na obiekt callera, który właśnie jest w użyciu (this).

0

Dziękuję wam za odpowiedzi!

Poprawka rincewinda sprawia, iż poniższy kod działa poprawnie:

 #include <functional>
#include <vector>
#include <algorithm>
#include <iostream>


class container {
	public:
		std::vector<int> vect; // [ 0, 1, 2, 3, 4 ]

	public:
		container() {
			for (int i = 0; i < 5; i++ )
				vect.push_back(i);
		}

		template<class Function>
			void each (Function f) {
				for_each(vect.begin(), vect.end(), f);
				std::cout << "\n";
			}
} o_container ;

class caller {
	public:
		void funkcja(int i) { std::cout << i; }
		void funkcja_ref(int& j ) { std::cout << j; }
		caller() {
			o_container.each ( std::bind1st( std::mem_fun(&caller::funkcja), this)  );

//			o_container.each ( std::bind1st( std::mem_fun_ref(&caller::funkcja_ref), this)  );	// Kompilator wyrzuca tonę błędów wskazując na STLa.

			std::cout << "\n";
		}
} o_caller;

int main() {
	return 0;
}

Mam jednak kolejny problem, zamieszczony w powyższym kodzie. Staram się w podobny sposób wywołać funkcję przekazującą argument przez referencję. Służyć do tego powinno mem_fun_ref z STLa.

W najprzeróżniejszych jednak kombinacjach nie działa - specyfikacja na cplusplus.com tłumaczy ten temat straszliwie po łebkach, nie jestem w stanie nigdzie znaleźć rozwiązania.

Wszystkie błędy kierują mnie do bibliotek STLa: stl_algo.h, binders.h etc. - nie mam więc żadnej wskazówki o co może chodzić.

Z góry dziękuję za poświęcony czas :)

1

Krótko: źle rozumiesz działanie mem_fun_ref(). Nie wpływa on na sposób przekazywania parametrów, nieważne, czy przesyłasz je przez wartość, referencję czy wskaźnik. W Twoim przypadku mem_fun_ref() nie ma żadnego zastosowania.

Dłużej: mem_fun() zwraca funktor, który jako pierwszego parametru oczekuje wskaźnika na obiekt klasy, do którego metody linkuje. Z kolei mem_fun_ref() zwraca funktor, którego pierwszym parametrem jest referencja do obiektu. Wydaje się to niewielką różnicą, ale w praktyce jest dość istotne. Szczególnie w funkcjach typu transform(), for_each() i innych. Przykład z cplusplus.com pokazuje te różnice:

mem_fun():

vector<string*> numbers; // zauwaz, ze wektor przechowuje *wskazniki* na string
vector<int> lengths(numbers.size());
// ...
transform(numbers.begin(), numbers.end(), lengths.begin(), mem_fun(&string::length)); // dlatego tutaj mem_fun() dziala, bo oczekuje wlasnie wskaznika do obiektu jako parametru

mem_fun_ref():

vector<string> numbers; // ...a tutaj wskaznikow nie ma
vector<int> lengths(numbers.size());
// ...
transform(numbers.begin(), numbers.end(), lengths.begin(), mem_fun_ref(&string::length)); // wiec tutaj potrzeba funktora pobierajacego referencje do obiektu

Sprawdź też tutaj: http://www.richelbilderbeek.nl/CppMem_fun_ref.htm

0

Dzięki za odpowiedź :}

Jednak jeśli mem_fun oczekuje wskaźnika na obiekt, dlaczego kod zamieszczony powyżej działa?

o_container.each ( std::bind1st( std::mem_fun(&caller::funkcja), this)  );

Wszak container::vect przechowuje <int>, nie <int*>.

Zaś wersja z mem_fun_ref zmusić do działania nie sposób, mimo, iż wskaźników nigdzie nie przechowuję.

Również nie sposób zmusić do działania funkcji funkcja_ref, pobierającej referencję na int'a (int&).

Niezależnie od tego, czy korzystam z bind1st (którego, notabene, działania tutaj zupełnie nie rozumiem -> czym jest pierwszy argument funkcji zwracanej przez mem_fun?), czy nie, czy z mem_fun czy z mem_fun_ref -> przekazać int'a przez referencję nie sposób...

#include <functional>
#include <vector>
#include <algorithm>
#include <iostream>


class container {
	public:
		std::vector<int> vect; // [ 0, 1, 2, 3, 4 ]

	public:
		container() {
			for (int i = 0; i < 5; i++ )
				vect.push_back(i);
		}

		template<class Function>
			void each (Function f) {
				for_each(vect.begin(), vect.end(), f);
				std::cout << "\n";
			}
} o_container ;

class caller {
	public:
		void funkcja(int i) { std::cout << i; }
		void funkcja_ref(int& j ) { std::cout << j; }
		caller() {
			// działa w porządku...
			o_container.each ( std::bind1st( std::mem_fun(&caller::funkcja), this)  );

			// nie działa zupełnie... W każdym przypadku zwraca błędy biblioteki STLa (stl_algo.h, binders.h, etc.)
			// o_container.each ( std::bind1st( std::mem_fun_ref(&caller::funkcja_ref), this)  );
			// o_container.each ( std::mem_fun_ref(&caller::funkcja_ref) );
			// o_container.each ( std::bind1st( std::mem_fun_ref(&caller::funkcja_ref), this)  );
			// o_container.each ( std::mem_fun(&caller::funkcja_ref)  );

			std::cout << "\n";
		}
} o_caller;

int main() {
	return 0;
}

0
o_container.each(std::bind1st(std::mem_fun(&caller::funkcja_ref), this));

Tak powinno działać, po prostu. Ale nie zadziała, bo w bibliotece standardowej jest błąd (błąd w specyfikacji) - mem_fun próbuje zrobić referencję do referencji. Możesz użyć albo wskaźnika, albo nowych funkcji std::bind z C++11.

1

[1] Funkcja z parametrem jako referencja -- po chwili googlania: http://stackoverflow.com/questions/5349973/why-did-it-compile-errorly-when-i-added-a-reference-argument-to-a-member-functio -- tutaj czytamy:

The problem is that internally, mem_fun tries to set as its argument type a const reference to the argument type of the member function. If you make then function take in a reference, then it tries to create a reference to a reference, which is illegal in C++.

Czyli nie da się poprzez mem_fun() ani mem_fun_ref() przekazać parametru jako referencji. To samo wewnętrznie robi mem_fun(), co powoduje próbę stworzenia referencji do referencji, co jest zabronione.

[2] Działanie funktorów, bindowania, etc: popatrz na to tak. Masz wskaźnik na metodę klasy (w Twoim przypadku caller::funkcja(int)). Metody są związane z obiektem, nie z klasą, dlatego potrzebny jest kontekst jej wywołania. Wobec tego:

void A::foo(T1, T2, ...) -> void F(T1, T2, ...)
Robimy mem_fun():
mem_fun(&A::foo) -> void F(caller*, T1, T2, ...)

Jak widać. mem_fun() dodaje na pierwszej pozycji w liście parametrów wskaźnik do kontekstu wywołania metody, czyli obiektu, na rzecz którego metoda zostanie wywołana.

W przykładach na cplusplus.com pokazane jest, jak używać metod mem_fun() i mem_fun_ref() na elementach przechowywanych w kontenerze:

vector<string> numbers;
vector<int> lengths;
// ...
transform(numbers.begin(), numbers.end(), lengths.begin(), mem_fun_ref(&string::length));

Przekazywana jest metoda klasy string, operuje się też na wektorze string. W tym wypadku dla każdego elementu w numbers wywołana zostanie metoda length() i dopisana do wektora lengths. Wszystko łatwe i przyjemne.

teraz: po co bind1st()? Najpierw -- czym jest bindowanie argumentów (właściwie od początku posługujemy się tu terminami związanymi z programowaniem funkcyjnym, warto wiedzieć). Bindowanie ustala jeden z argumentów funkcji na jakąś stałą wartość, powodując zmniejszenie liczby parametrów. Kilka przykładów:

bind1st(void F(int, double, string), 666) -> void F(double, string) // "bind first" -- pierwszy parametr funkcji ustalony na 666, zwracana jest funkcja dwuargumentowa
bind2nd(void F(int, double, string), 6.66) -> void F(int, string) // "bind second" -- drugi parametr funkcji ustalony na 6.66, zwracana jest funkcja dwuargumentowa
bind3rd(void F(int, double, string), "Devil") -> void F(int, double) // "bind third" -- trzeci parametr funkcji ustalony na "Devil", zwracana jest funkcja dwuargumentowa

Jeszcze raz -- po co to? Ano dlatego, że w Twoim przypadku do for_each() trafia metoda klasy, która nie jest przechowywana w kontenerze. Wobec tego należy nadać jej właściwy kontekst. Tym kontekstem jest wskaźnik na obiekt callera. Jako, że w momencie wywoływania metody container::each() znajdujemy się w konstruktorze o_caller, więc bindujemy metodę mem_ref(&caller::funkcja) z wskaźnikiem na obiekt callera -- czyli this. W ten sposób do metody container::each() wysyłamy funkcję jednoargumentową, i for_each() nie będzie próbował bindować pierwszego parametru na aktualnie przetwarzany obiekt z kontenera.

[3] Różnica między mem_fun() a mem_fun_ref():

mem_fun(void A::foo(T1, T2, ...)) -> void F(A*, T1, T2, ...)
mem_fun_ref(void A::foo(T1, T2, ...)) -> void F(A&, T1, T2, ...)

Do bindowania "ręcznego", jak w Twoim przypadku, mem_fun_ref() się nie nadaje z tego samego powodu, z którego nie można przesyłać parametrów przez referencję. Znajduje jednak zastosowanie w funkcjach bibliotecznych, a różnicę w zastosowaniu mem_fun() i mem_fun_ref() można zobaczyć na cplusplus.com.

1 użytkowników online, w tym zalogowanych: 0, gości: 1