2016-02-03 19 views
16

Considerate questo codice:C++ Stati Puntatore Funzione Definizione

#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(); 
} 

Sembra che la versione successiva non accetta funzioni con qualificazioni cv in più.

Qual è la differenza tra la specifica di un puntatore membro della funzione come questo C (M::*member)() e come questo C (M::*member)?

+0

Non sapevo nemmeno che 'C (M :: * member)' è una sintassi valida ... –

+0

Il punto è che 'call1' non accetta le funzioni con qualificatori cv, mentre' call2' lo consente. – Crossfire

+1

Non correlato, ma perché preoccuparsi di usare 'std :: bind' invece di chiamare direttamente la funzione membro? '(istanza -> * membro)();' –

risposta

15

Semplifichiamo a considerare solo la differenza tra:

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

In f, member è un puntatore a una funzione membro di M ritorno prendendo zero argomenti e ritorno C. Se lo hai chiamato con &B::func, il compilatore dedurrà M == B e C == void. Semplice.

In g, member è solo un puntatore a un membro di M di tipo C. Ma, nel nostro caso, &B::func è una funzione. Quindi l'impatto qui è solo cadere il puntatore. Deduciamo di nuovo M == B, mentre C diventa void() - ora C è un tipo di funzione. Questa è una versione meno specializzata in quanto consente più tipi di membri. g può essere confrontato con funzioni che accettano argomenti o contro puntatori ai membri o, in modo rilevante, contro cv - functinos membri qualificati.

Prendiamo come esempio una funzione di sovraccarico e come si otterrebbe dedotto in modo diverso (questo era il problema originale nella sua interrogazione, che da allora è stato modificato, ma è comunque interessante):

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

Quando abbiamo do:

f(&X::bar); 

Anche se &X::bar è un nome sovraccarico, una sola realtà corrisponde C (M::*)(). Quello che ha M == X e C == void. Semplicemente non c'è modo che il sovraccarico di bar prendendo un int corrisponda al tipo di modello. Quindi questo è uno degli usi accettabili del passaggio di un nome sovraccarico. Questo deduce bene.

Tuttavia, quando facciamo:

g(&X::bar); 

Ora, ci sono due deduzioni perfettamente validi a partire. C potrebbe essere sia void() sia void(int). Poiché entrambi sono validi, la deduzione è ambigua e non si riesce a compilare, con un errore che non lo rende particolarmente chiaro.


Ora torna al tuo esempio:

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

Il tipo di &B::func2 è void (B::*)() const volatile. Poiché call1 deduce un tipo di funzione membro che non accetta argomenti e non è qualificato per il cv, la deduzione del tipo semplicemente fallisce.Non c'è C o M per ottenere quei tipi per abbinare.

Tuttavia, la deduzione call2 va bene poiché è più generica. Può corrispondere a qualsiasi puntatore per membro. Semplicemente finiamo con C = void() const volatile. Ecco perché funziona.

+0

C'è un modo per creare un puntatore a funzione, che può accettare sovraccarichi di parametri ma ignorerebbe i qualificatori di cv? Cioè, la versione generalizzata 'C (M :: * member)' accetta gli argomenti e ignora i qualificatori di cv. Se si specificano argomenti cioè 'C (M :: * membro) (A1, A2)', ho bisogno di sovraccaricare queste funzioni del chiamante con 'C (M :: * membro) (A1, A2) const' e' C (M :: * membro) (A1, A2) volatile' rispettivamente. – Crossfire

+1

@Crossfire Basta creare un tipo di carattere come ... 'modello struct is_func_const: std :: false_type {}; template struct is_func_const : std :: true_type {}; template struct is_func_const : std :: true_type {}; 'e quindi puoi usare SFINAE. – Barry

Problemi correlati