2016-03-24 19 views
6

Ho una conoscenza di base di SFINAE, ad es. come funziona enable_if. Di recente mi sono imbattuto nello this answer e ho passato più di un'ora a cercare di capire come funziona in realtà senza alcun risultato.SFINAE Basato sull'esistenza/assenza di un membro della classe

L'obiettivo di questo codice è di sovraccaricare una funzione in base al fatto che una classe abbia un membro specifico in essa. Ecco il codice copiato, utilizza C++ 11:

template <typename T> struct Model 
{ 
    vector<T> vertices; 

    void transform(Matrix m) 
    { 
     for(auto &&vertex : vertices) 
     { 
      vertex.pos = m * vertex.pos; 
      modifyNormal(vertex, m, special_()); 
     } 
    } 

private: 

    struct general_ {}; 
    struct special_ : general_ {}; 
    template<typename> struct int_ { typedef int type; }; 

    template<typename Lhs, typename Rhs, 
      typename int_<decltype(Lhs::normal)>::type = 0> 
    void modifyNormal(Lhs &&lhs, Rhs &&rhs, special_) { 
     lhs.normal = rhs * lhs.normal; 
    } 

    template<typename Lhs, typename Rhs> 
    void modifyNormal(Lhs &&lhs, Rhs &&rhs, general_) { 
     // do nothing 
    } 
}; 

Per la vita di me, non posso avvolgere la mia testa intorno a come funziona questo meccanismo. Nello specifico, cosa ci aiuta a fare typename int_<decltype(Lhs::normal)>::type = 0 e perché abbiamo bisogno di un tipo aggiuntivo (special_/general_) in questo metodo.

+0

Per me sembra che l'esempio non sia valido; il riferimento di inoltro non attiva 'decltype (Lhs :: normal)' se 'Lhs' è un lvalue. Dovrebbe essere 'decltype (std :: declval () .normal)' –

+0

@PiotrSkotnicki L'esempio esegue, l'ho provato. – Phonon

+0

Compila, ma hai controllato se dà un risultato corretto? –

risposta

3

perché abbiamo bisogno di un tipo di extra (special_/general_) in questo metodo

questi sono utilizzati al solo fine di consentire la funzione modifyNormal ad essere sovraccaricato con diverse implementazioni. Che cosa è speciale su di essi è che special_ utilizza la relazione IS-A poiché eredita da general_. Inoltre, la funzione transform chiama sempre il sovraccarico modifyNormal che prende il tipo special_, vedere la parte successiva.

cosa vuol typename int_<decltype(Lhs::normal)>::type = 0 aiutarci a fare

Si tratta di un argomento di un template con un valore predefinito. Il valore predefinito è presente in modo che la funzione transform non debba specificarlo, il che è importante perché l'altra funzione modifyNormal non ha questo parametro modello. Inoltre, questo parametro template viene aggiunto solo per richiamare SFINAE.

http://en.cppreference.com/w/cpp/language/sfinae

Quando si sostituisce il tipo dedotta per il parametro modello fallisce, la specializzazione viene scartato dal sovraccarico di set invece di provocare un errore di compilazione.

Quindi, se un guasto si verifica la funzione modifyNormal prendendo il tipo special_ viene rimosso dal set di sovraccarichi da considerare. Questo lascia solo la funzione modifyNormal prendendo un tipo general_ e dal momento che special_ IS-A general_ tipo tutto funziona ancora.

Se non si verifica un errore di sostituzione, verrà utilizzata la funzione modifyNormal utilizzando il tipo special_ poiché è una corrispondenza migliore.


Nota: Il general_ tipo è un struct così l'eredità è public di default, consentendo la IS-Un rapporto senza utilizzare la parola chiave public.


Edit:

Puoi commentare perché usiamo l'elaborato typename int_<decltype(Lhs::normal)>::type meccanismo, in primo luogo?

Come indicato sopra, viene utilizzato per attivare il comportamento di SFINAE. Tuttavia, non è molto elaborato quando lo abbatti. Al suo cuore si vuole creare un'istanza di un istanza del int_ struct per un certo tipo T e ha un tipo di dati type definito:

int_<T>::type 

Dal momento che questo viene utilizzato in un modello la parola chiave typename deve essere aggiunto, vedere When is the “typename” keyword necessary?.

typename int_<T>::type 

Infine, qual è il tipo effettivo utilizzato per creare un'istanza del int_ struct? Questo è determinato da decltype(Lhs::normal), che riporta il tipo per Lhs::normal. Se il tipo di tipo Lhs ha un membro di dati normal, allora tutto ha esito positivo. Tuttavia, in caso contrario, si verifica un errore di sostituzione e l'importanza di ciò è spiegata sopra.

+0

Puoi commentare perché utilizziamo il meccanismo elaborato 'typename int_ :: type' in primo luogo? Non c'è un modo più semplice per farlo fallire? – Phonon

+0

@Phonon Ho aggiunto ulteriori informazioni per provare a chiarire i dettagli per te. –

1

special_/general_ sono tipi utilizzati per consentire al compilatore di distinguere tra i due metodi modifyNormal. Si noti che il terzo argomento non viene utilizzato. L'implementazione generale non fa nulla, ma nel caso specifico modifica il normale. Si noti inoltre che special_ deriva da general_. Ciò significa che se la versione specializzata di modifyNormal non è definita (SFINAE) si applica il caso generale; se esiste la versione specializzata, allora verrà scelta (è più specifica).

Ora c'è un interruttore nella definizione di modifyNormal; se il tipo (primo argomento del modello) non ha un membro chiamato normal allora il modello fallisce (SFINAE e non si lamenta, questo è il trucco di SFINAE) e sarà l'altra definizione di modifyNormal che verrà applicata (il generale Astuccio). Se il tipo definisce un membro denominato normal, il terzo argomento del modello può essere risolto con un ulteriore modello di argomento terzo (un valore predefinito di int equivale a 0). Questo terzo argomento non ha scopo per la funzione, solo per SFINAE (il modello si applica su di esso).

1

I tipi general e special vengono utilizzati per forzare il compilatore a scegliere la prima funzione (ovvero una corrispondenza migliore) come prima prova durante il tentativo di risolvere la chiamata.
noti che la chiamata è:

modifyNormal(vertex, m, special_()); 

Comunque, poiché general eredita da special, entrambi sono validi e la seconda saranno scelti se la prima fallisce durante tipo di sostituzione.

Quindi, è come dire - facciamo un tentativo come se quel metodo effettivamente esiste, ma mantieni la calma perché abbiamo un callback catch-all che non fa nulla.

Perché potrebbe fallire?
Ecco dove int_ prende la sua parte nel gioco.
Se il decltype (lasciatemi dire) dà un errore perché il metodo membro normal è mancato in Lhs, int_ non può essere specializzata e la sostituzione non riesce in realtà, ma grazie a SFINAE, questo fallimento non è un errore finché ci Esiste un'altra sostituzione che può essere provata (ed esiste nel nostro caso, il metodo che ha general come argomento, che è ancora una corrispondenza meno precisa per la chiamata originale).

Problemi correlati