[C++] Trywialna konwersja - wskaźnik do funkcji


(Quentin) #1

Witam!

Mam pytanie odnośnie trywialnych konwersji (które zachodzą między innymi przy dopasowaniu dokładnym argumentów do wersji funkcji przeładowanej) w C++. W Symfonii jest napisane, że oprócz np. takich konwersji:

T[] ---------> T*

T ---------> T&

itp., zalicza się do konwersji trywialnej również coś takiego:

T(argum) ---------> (*T)(argum)

Jako przykład wywołania funkcji jest pokazane takie coś:

fun(funkcyjka); [/code]

a jako przykład deklaracji (czyli 2-gi człon, ten po strzałce):

[code=php]fun((*wskfun)(int)); 

Typ zwracany w funkcji fun może zostać pominięty bo i tak nie ma on tutaj znaczenia.

Ale w ogóle co to za konstrukcja :?: Wskaźnik do funkcji wcale nie ma w swojej definicji określone jakiego typu wartość zwraca funkcja pokazywana przez niego...


(Sawyer47) #2

Gdzie tak przeczytałeś? Nie pomyliło Ci się z przeciążaniem/przeładowaniem (ang. overload)?

W Symfonii jest napisane jasno "Jeśli argumentem wywołania jest wskaźnik do jakiejś funkcji, to kompilator dopasuje tylko funkcję, która ma dokładnie taki sam typ wskaźnika. Żadnych awansów, konwersji".

Natomiast podejrzewam, że faktycznie autor popełnił błąd, pisząc

Zapewne chodziło o

T(argnum) → T(*)(argnum)

Oraz faktycznie w przykładzie powinien być jakiś ty zwracany.


(Quentin) #3

Właśnie chodziło mi tu o kontekst przeładowania :slight_smile:

Nie rozumiem tego Twojego zapisu... Mógłbyś pokazać go na przykładzie :?:

EDIT:

Będąc przy wskaźnikach to też chciałbym zwrócić uwagę na to, że w tym samym rozdziale w Symfonii jest następny błąd na str. 407 (II wydanie). Jest tam napisane, że:

Jest to oczywiście bzdura.


(Sawyer47) #4

Proszę

#include 


void f() { puts("Hello"); }


int main()

{

	typedef void FunType();

	typedef void(*FunPtr)();

	typedef void(&FunRef)();

	FunType* fptr0 = f;

	FunPtr fptr1 = f;

	FunPtr fptr2 = &f;

	FunRef fref1 = f;

	FunType& fref2 = f;

	f();

	fptr0();

	fptr1();

	fptr2();

	fref1();

	fref2();

}

Definiuję funkcję, której typ to void(). Wskaźnik na tę funkcję to void(*)(), a referencja do niej to void(&)(). Zupełnie analogicznie do typów prostych, np. int, int*, int& – jedynie składnia jest taka jaka jest w przypadku funkcji, że wymagany jest nawias wokół asterysku/ampersandu. Natomiast co do konwersji wspominanej w Symfonii, tutaj po prostu zapis f jest niejawnie konwertowane na wskaźnik do funkcji, więc nie trzeba pisać &f (choć, jak widać, można).

//edit:

Na problemy z deklaracjami, polecam program cdecl, jest też dostępny on-line: http://www.cdecl.org/

Chociaż w praktyce lepiej pomóc sobie typedefami, tak jak w przykładzie, wtedy można mieć np. wskaźnik do czegoś skomplikowanego wg standardowej składni Typ*, gdzie Typ to typedef czegoś skomplikowanego :wink:


(Quentin) #5

OK, więc w C++ jest tak, że każde wyrażenie wywołania funkcji:

funkcja(6, 'c'); [/code]

[code=php]&funkcja(6, 'c'); 

zostanie zamienione na:

(*funkcja)(6, 'c'); [/code]

Czyli będzie to jakby "skok" do tego adresu (miejsca) w pamięci, gdzie zaczyna się dana funkcja. Co ma jednak to wspólnego z mechanizmem przeładowania, a tym bardziej z konwersjami ?

[code=php]tablica[4] 

też jest zamieniane na

*(tablica + 4) [/code]

ale jaka tu konwersja (typu) zachodzi niby :?: Żadna...

Po co więc autor umieścił takie coś w tej tabeli z dopasowaniami z trywialną konwersją :?:


(Sawyer47) #6

Skąd wytrzasnąłeś tę zamianę? Jeżeli chodzi o konwersję to funkcja można traktować jako (&funkcja), czyli "nazwa funkcji" jest konwertowana na jej adres, a więc konwersja void() → void(*)(). Co do twojego zapisu, zarówno funkcja(), (&funkcja)(), (*funkcja)() wywołają funkcje, ale po co gmatwać sobie zapis?

Analogicznie przy tablicy, jej "nazwa" to adres pierwszego elementu, a więc konwersja T[] → T* (choć sama tablica jest typu T[]).


(Quentin) #7

Aha, rozumiem mniej więcej - autorowi chodziło o zamianę typu funkcji na wskaźnik na taki typ...

Tyle, że jak wspomniałeś, powinien napisać T(argnum) → T(*)(argnum).

Jak dla mnie jest to dziwne, a jeszcze dziwniejszy jest fakt, że jest to dopasowanie (w kontekście przeładowania) z konwersją trywialną a nie po prostu dopasowanie dokładne...

EDIT (11.02.2009):

Chociaż nie wiem czy autor się tu pomylił :? W MSDN Library jest tak samo:

:arrow: http://msdn.microsoft.com/en-us/library ... 71%29.aspx

O ile pierwszy typ jest typem funkcji, to drugi typ jest troszkę dziwny... Co o tym sądzicie :?:


([alex]) #8

Niema nic bardziej szkodliwego niż nazewnictwo którego jest więcej niż tego co nazywa, to ewidentne zwycięstwo technologi nad rozsądkiem.

w tym wszystkim chodzi tylko o następujące:

void f() { }

void (*w)()=f; // możesz ale nie musisz pisać =&f

w(); // możesz ale nie musisz pisać (*w)();


(Quentin) #9

Aha, czyli jest tak jak napisał nr47. Nazwa funkcji może zostać zamieniona na wskaźnik do niej. Czyli skoro to jest konwersja trywialna, to dopasowanie dokładne (nie-trywialne) wyglądało by tak: (pytam z ciekawości) :?:

void fun(int (*wsk)(double, char))  }

([alex]) #10

Aby wymyślić dodatkowe nazewnictwo dla książki, aby książka miała więcej stron, aby dostać większy honorarium?


(Sawyer47) #11

Bez przesady, to raczej nazewnictwo standardu C++. A do Quentina: dopóki wiesz jak co działa i potrafisz tego użyć, naprawdę nie ma się co przejmować czy coś się nazywa tak czy inaczej oraz w jakiej jest tabelce. Ja np. dopiero po tym gdy założyłeś ten wątek odkryłem, że można napisać (*******funkcja)() – ale taka wiedza do niczego potrzebna nie jest, chyba tylko do pisania kodu na IOCCC :wink:.


(Quentin) #12

@[alex]

Mówiłem tu też o tej stronie...

:arrow: http://msdn.microsoft.com/en-us/library ... 71%29.aspx


([alex]) #13

Na tej stronie akurat wszystko w jednej tabelce.


(Quentin) #14

Dobra, whatever ^.^

Ponieważ jestem pedantem zapytam się: dlaczego :?: Zapis:

(*fun)(5, 'd', 3.14); [/code]

jest w miarę zrozumiały, bo oznacza on mniej więcej, żeby dokonać dereferencji na wskaźniku, czyli w tym przypadku "skoczyć" pod adres gdzie znajduje się ciało funkcji. Ale zapis z wieloma asteryskami jest dziwny, bo budzi on skojarzenia, że fun jest wskaźnikiem na wskaźnik pokazujący na wskaźnik ... itd.


(Sawyer47) #15

Nie wiem czy tak mówi standard, ale u mnie na g++ ten kod daje cały czas ten sam adres:

#include 


void f() {}


int main()

{

	printf("%p. %p, %p, %p\n", f, *f, **f,*** f);

	printf("%p. %p, %p, %p\n", &f, *&f, &*f, *&*f);

}

//edit Na wysokich ustawieniach ostrzeżeń, wyrzuca

$ g++ k.cpp -pedantic -Wall -Wextra -Weffc++

k.cpp: In function ‘int main()’:

k.cpp:7: warning: format ‘%p’ expects type ‘void*’, but argument 2 has type ‘void (*)()’

k.cpp:7: warning: format ‘%p’ expects type ‘void*’, but argument 3 has type ‘void (*)()’

k.cpp:7: warning: format ‘%p’ expects type ‘void*’, but argument 4 has type ‘void (*)()’

k.cpp:7: warning: format ‘%p’ expects type ‘void*’, but argument 5 has type ‘void (*)()’

k.cpp:8: warning: format ‘%p’ expects type ‘void*’, but argument 2 has type ‘void (*)()’

k.cpp:8: warning: format ‘%p’ expects type ‘void*’, but argument 3 has type ‘void (*)()’

k.cpp:8: warning: format ‘%p’ expects type ‘void*’, but argument 4 has type ‘void (*)()’

k.cpp:8: warning: format ‘%p’ expects type ‘void*’, but argument 5 has type ‘void (*)()’

Czyli wygląda na to, że każdy z tych zapisów jest traktowany jako ‘void (*)()’


(Quentin) #16

Aha, czyli te gwiazdki pozostałe są jakby ignorowane i tak. Dzięki :wink: