2016-02-03 23 views
16

Rozważmy następujący kod:wskaźnik funkcji C++ Użytkownik Definicja

#include <iostream> 
#include <functional> 

struct B { 
    template <class C, class M, class T> 
    void call1(C (M::*member)(), T *instance) { 
     std::function<void()> fp = std::bind(member, instance); 
     fp(); 
    } 

    template <class C, class M, class T> 
    void call2(C (M::*member), T *instance) { 
     std::function<void()> fp = std::bind(member, instance); 
     fp(); 
    } 

    void foo() { 
     call1(&B::func, this); // works 
     call2(&B::func, this); // works 

     call1(&B::func2, this); // Error: no matching member function for call to 'call2' 
     call2(&B::func2, this); // works 
    } 

    void func() { 
     std::cout << "func\n"; 
    } 

    void func2() const volatile { 
     std::cout << "func2\n"; 
    } 
}; 

int main() { 
    B{}.foo(); 
} 

Wydaje się, że nowsza wersja nie akceptują funkcje z dodatkowych kwalifikatorów CV.

Jaka jest różnica między określeniem wskaźnika elementu funkcji podobnego do tego C (M::*member)() i jak ten C (M::*member)?

+0

Nie wiedziałem nawet, że 'C (M :: * member)' jest prawidłową składnią ... –

+0

Chodzi o to, że 'call1' nie przyjmuje funkcji z kwalifikatorami cv, a' call2' pozwala na to. – Crossfire

+1

Niepowiązane, ale po co zawracać sobie głowę użyciem 'std :: bind' zamiast bezpośrednio wywoływać funkcję member? '(instance -> * member)();' –

Odpowiedz

15

Załóżmy uprościć po prostu biorąc pod uwagę różnicę między:

template <class C, class M> void f(C (M::*member)()); 
template <class C, class M> void g(C (M::*member)); 

W f, member jest wskaźnikiem do funkcji członkowskim M powracającego przy zerowych argumenty i powrocie C. Jeśli wywołasz go pod numerem &B::func, kompilator wyprowadzi M == B i C == void. Bezpośredni.

W g, member jest tylko wskaźnikiem do elementu M typu C. Ale w naszym przypadku &B::func jest funkcją. Więc wpływ tutaj polega na porzuceniu wskaźnika. Ponownie dedukujemy M == B, natomiast C staje się void() - teraz C jest typem funkcji. Jest to mniej wyspecjalizowana wersja niż f, ponieważ pozwala na więcej rodzajów członków. g można dopasować do funkcji, które pobierają argumenty lub do wskaźników do członków, lub, odpowiednio, do cv -wyprofilowanych funkcjach członkowskich.

Rozważmy jako przykład przeciążony funkcja i jak to się wywnioskować inaczej (to był oryginalny problem w swoim pytaniu, który został edytowany, ale nadal jest interesujący):

struct X { 
    void bar() { } 
    void bar(int) { } 
}; 

kiedy zrobić:

f(&X::bar); 

Nawet &X::bar jest przeciążony nazwa, tylko jeden faktycznie pasuje C (M::*)(). Ten, który ma M == X i C == void. Po prostu nie ma mowy, aby przeciążenie z bar przyniosło int dopasowanie do typu szablonu. Jest to jedno z dopuszczalnych zastosowań przekazywania przeciążonej nazwy. To prowadzi do grzywny.

Jednak gdy robimy:

g(&X::bar); 

Teraz istnieje dwa doskonale vaild potrącenia. C może być zarówno void() i void(int). Ponieważ oba są prawidłowe, dedukcja jest niejednoznaczna i nie można skompilować - z błędem, który tak naprawdę nie czyni tego szczególnie wyraźnie.


Wracając do przykładu:

call1(&B::func2, this); // Error: no matching member function for call to 'call2' 
call2(&B::func2, this); // works 

Rodzaj &B::func2 jest void (B::*)() const volatile. Od call1 wywodzi się na typ funkcji członka, który nie przyjmuje argumentów i nie jest kwalifikowany cv, odliczenie typu po prostu nie powiedzie się.Nie ma żadnego C lub M, aby dopasować te typy.

Odliczenie call2 jest w porządku, ponieważ jest bardziej ogólne. Może on pasować do każdego wskaźnika o wartości dowolnej. Po prostu kończymy z C = void() const volatile. Właśnie dlatego to działa.

+0

Czy istnieje sposób na utworzenie wskaźnika funkcji, który może akceptować przeciążenia parametrów, ale zignorowałby kwalifikatory cv? Oznacza to, że uogólniona wersja 'C (M :: * member)' akceptuje argumenty i ignoruje kwalifikatory cv. Jeśli podam argumenty, np. 'C (M :: * member) (A1, A2)', muszę przeciążyć te funkcje wywołujące za pomocą 'C (M :: * member) (A1, A2) const' i' C (M :: * członek) (A1, A2) volatile' odpowiednio. – Crossfire

+1

@Crossfire Po prostu wpisz cechę typu ... 'szablon struct is_func_const: std :: false_type {}; szablon struct is_func_const : std :: true_type {}; szablon struct is_func_const : std :: true_type {}; ', a następnie możesz użyć SFINAE. – Barry