2012-06-02 25 views
5

Załóżmy, że mam funkcja, która wykonuje jakiś efekt uboczny, a następnie zwraca odpowiedź:wskaźniki funkcyjne i typ zwracany konwersje

int foo() 
{ 
    perform_some_side_effect(); 
    return 42; 
} 

chcę wiązać foo do wskaźnika funkcji, ale nie jestem zainteresowany w odpowiedź, tylko efekt uboczny:

void (*bar)() = foo; 

to jednak wydaje się być błąd typ:

error: invalid conversion from ‘int (*)()’ to ‘void (*)()’ 

Jakie są przesłanki tego błędu? Dlaczego system typów nie pozwala mi zignorować odpowiedzi?


Na marginesie, to działa, jeśli mogę zawinąć wskaźnik funkcji w std::function:

std::function<void()> baz = foo; 

Jak std::function (podobno) udało się obejść to ograniczenie w systemie typu?

+1

Racjonalne jest tak, że są różne rodzaje. Możesz to zmienić (na własne ryzyko) za pomocą 'reinterpret_cast'. –

+0

@StevenBurnap: Pytanie brzmi: * dlaczego * nie można dokonać konwersji. Jest oczywiste, że są to różne typy. 'Float' również nie jest' int', ale możesz je konwertować. – Puppy

+0

Domyślam się, że tak jest, ponieważ z zasady niejawne rzucanie wskaźników jest niedozwolone. –

Odpowiedz

9

What is the rationale behind that error? Why doesn't the type system allow me to ignore the answer?

Powód jest taki, że typy są różne, a wygenerowany kod w miejscu połączenia (za pomocą wskaźnika funkcji) jest inny. Rozważmy konwencję wywołującą, w której wszystkie argumenty są zapisywane na stosie, a przestrzeń dla wartości zwracanej jest również zarezerwowana w stosie. Jeśli wywołanie przechodzi przez void (*)(), to nie zostanie zarezerwowane żadne miejsce w stosie dla wartości zwracanej, ale funkcja (nieświadoma tego, jak jest wywoływana) nadal będzie zapisywać wartość 42 w miejscu, w którym dzwoniący powinien mieć zarezerwowane miejsce.

How does std::function (apparently) manage to circumvent this restriction in the type system?

Nie dotyczy. Tworzy obiekt funkcji, który owija wywołanie do rzeczywistej funkcji. Będzie ona zawierać człon takiego:

void operator()() const { 
    foo(); 
} 

Teraz gdy kompilator przetwarza wezwanie do foo wie, co ma zrobić, aby rozmowy funkcję, która zwraca się int i uczyni to zgodnie z konwencją wywołującego . Ponieważ szablon nie zwraca, po prostu zignoruje wartość - która została zwrócona.

+1

+1: Najbardziej kompletna odpowiedź. –

1

std::function musi być kompatybilny tylko ze źródłami - to znaczy, może wygenerować nową klasę, która generuje nowy kod, który ignoruje wynik. Wskaźnik funkcji musi być zgodny binarnie i nie może wykonać tego zadania - void(*)() i int(*)() wskazują dokładnie ten sam kod.

1

Można myśleć o std::function<> robi to dla konkretnego przypadku:

void __func_void() 
{ 
    foo(); 
} 

To rzeczywiście nieco bardziej skomplikowane, ale chodzi o to, że generuje kod szablonu wraz z typu skasowaniem nie dbać o szczegóły.

1

Oprócz tego, co inni mówili, osoba wywołująca również potrzebuje typu zwracanego, aby wiedzieć, jaki destruktor powinien wywoływać wynik (wartość zwracana może być tymczasowa).


Niestety nie jest tak proste jak

auto (*bar)() = foo; 

Chociaż GCC i Clang zaakceptować. Muszę ponownie sprawdzić specyfikację, aby sprawdzić, czy to rzeczywiście jest poprawne.

Aktualizacja: Spec mówi

The auto type-specifier signifies that the type of a variable being declared shall be deduced from its initializer or that a function declarator shall include a trailing-return-type.

może to być mylące, gdy czytać szybko, ale ten jest realizowany przez GCC i brzękiem zastosowanie tylko do będąc w głównym declarator. W naszym przypadku jest to deklarator wskaźnika. Deklarator w niej zagnieżdżony jest deklaratorem funkcji. Więc wystarczy podstawić auto dla void, a następnie kompilator wyprowadzi typ dla ciebie.


Nawiasem mówiąc, zawsze można dokonać tej pracy ręcznie, ale to zajmuje trochę sztuczek, aby to działało

template<typename FunctionType> 
struct Params; 

template<typename ...Params> 
struct Params<void(Params...)> { 
    template<typename T> 
    using Identity = T; 

    template<typename R> 
    static Identity<R(Params...)> *get(R f(Params...)) { 
    return f; 
    } 
}; 

// now it's easy 
auto bar = Params<void()>::get(foo); 
+0

Nawet to kompiluje się w VS2011 RC. auto bar = Test; \t bar(); – Jagannath

Powiązane problemy