2012-07-04 13 views
8

Ho un SFINAE problema:SFINAE: compilatore non seleziona il modello di classe specializzato

Nel codice seguente, voglio che il compilatore C++ di scegliere il funtore specializzata e della stampa "speciale", ma è la stampa "generale " anziché.

#include <iostream> 
#include <vector> 

template<class T, class V = void> 
struct Functor { 
    void operator()() const { 
    std::cerr << "general" << std::endl; 
    } 
}; 

template<class T> 
struct Functor<T, typename T::Vec> { 
    void operator()() const { 
    std::cerr << "special" << std::endl; 
    } 
}; 

struct Foo { 
    typedef std::vector<int> Vec; 
}; 

int main() { 
    Functor<Foo> ac; 
    ac(); 
} 

Come posso risolvere il problema in modo che lo struct specializzata viene automaticamente utilizzato? Nota Non desidero specializzare direttamente la struttura Functor su Foo, ma desidero specializzarla su tutti i tipi che hanno un tipo Vec.

P.S .: Sto usando g ++ 4.4.4

+0

Rimosso il tag 'compilatore', viene generalmente utilizzato per domande sul processo di compilazione stesso, mentre questa domanda riguarda il linguaggio C++. –

risposta

11

Ci scusiamo per fuorviante nell'ultima risposta, ho pensato per un momento che sarebbe stato più semplice. Quindi cercherò di fornire una soluzione completa qui. L'approccio generale per risolvere questo tipo di problemi è quello di scrivere un modello tratti aiutante e utilizzarlo insieme a enable_if (sia C++ 11, spinta o implementazione manuale) per decidere una classe di specializzazione:

Trait

Un approccio semplice, non necessariamente il migliore, ma semplice da scrivere sarebbe:

template <typename T> 
struct has_nested_Vec { 
    typedef char yes; 
    typedef char (&no)[2]; 
    template <typename U> 
    static yes test(typename U::Vec* p); 
    template <typename U> 
    static no test(...); 

    static const bool value = sizeof(test<T>(0)) == sizeof(yes); 
}; 

l'approccio è semplice, fornisce due funzioni template, che i tipi di ritorno di diverse dimensioni. Uno dei quali accetta il tipo nidificato Vec e l'altro accetta ellissi. Per tutti quei tipi che hanno un nidificato Vec il primo sovraccarico è una corrispondenza migliore (i puntini di sospensione sono la peggiore corrispondenza per qualsiasi tipo). Per quei tipi che non hanno un nidificato, Vec, SFINAE scarterà quel sovraccarico e l'unica opzione rimasta sarà l'ellissi. Quindi ora abbiamo un tratto per chiedere se un tipo ha un tipo nidificato Vec.

Attiva se

è possibile utilizzare questo da qualsiasi libreria, o si può rotolare il proprio, è abbastanza semplice:

template <bool state, typename T = void> 
struct enable_if {}; 

template <typename T> 
struct enable_if<true,T> { 
    typedef T type; 
}; 

Quando il primo argomento è false, il modello di base è l'unica opzione, e che non ha un nidificato type, se la condizione è true, quindi enable_if ha un nidificato type che possiamo usare con SFINAE.

Attuazione

Ora abbiamo bisogno di fornire il modello e la specializzazione che utilizzerà SFINAE solo per quei tipi con un nidificato Vec:

template<class T, class V = void> 
struct Functor { 
    void operator()() const { 
     std::cerr << "general" << std::endl; 
    } 
}; 
template<class T> 
struct Functor<T, typename enable_if<has_nested_Vec<T>::value>::type > { 
    void operator()() const { 
     std::cerr << "special" << std::endl; 
    } 
}; 

Ogni volta istanziamo Functor con un tipo, la il compilatore proverà a utilizzare la specializzazione, che a sua volta istanziare has_nested_Vec e ottenere un valore di verità, passato a enable_if. Per quei tipi per i quali il valore è false, enable_if non ha un tipo nidificato type, quindi la specializzazione verrà eliminata in SFINAE e verrà utilizzato il modello di base.

vostro caso particolare

Nel tuo caso particolare in cui sembra che non si ha realmente bisogno di specializzarsi tipo intero, ma solo l'operatore, è possibile mescolare i tre elementi in uno solo: un Functor che invia a una delle due funzioni interne templated in base alla presenza di Vec, eliminando la necessità di enable_if e la classe tratti:

template <typename T> 
class Functor { 
    template <typename U> 
    void op_impl(typename U::Vec* p) const { 
     std::cout << "specialized"; 
    } 
    template <typename U> 
    void op_impl(...) const { 
     std::cout << "general"; 
    } 
public: 
    void operator()() const { 
     op_impl<T>(0); 
    } 
}; 
2

Anche se questa è una vecchia questione, penso che sia ancora la pena di fornire una coppia più alternative per il fissaggio rapido del codice originale.

Fondamentalmente, il problema non è con l'uso di SFINAE (che parte va bene, in realtà), ma con l'abbinamento del parametro predefinito nel modello primario (void) per l'argomento fornito nella specializzazione parziale (typename T::Vec) . A causa del parametro predefinito nel modello principale, significa in realtà Functor<Foo, void>. Quando il compilatore tenta di creare un'istanza che utilizza la specializzazione, tenta di far corrispondere i due argomenti con quelli nella specializzazione e fallisce, poiché void non può essere sostituito per std::vector<int>. Quindi torna alla creazione di istanze utilizzando il modello principale.

Quindi, la soluzione più rapida, che si assume tutte le Vec s sono std::vector<int> s, è quello di sostituire la linea

template<class T, class V = void> 

con questo

template<class T, class E = std::vector<int>> 

sarà ora utilizzato La specializzazione, perché il gli argomenti corrisponderanno. Semplice, ma troppo limitante. Chiaramente, dobbiamo controllare meglio il tipo di argomento nella specializzazione, in modo da farlo corrispondere a qualcosa che possiamo specificare come parametro predefinito nel modello principale. Una soluzione rapida che non richiede la definizione di nuovi tratti è questo:

#include <iostream> 
#include <vector> 
#include <type_traits> 

template<class T, class E = std::true_type> 
struct Functor { 
    void operator()() const { 
    std::cerr << "general" << std::endl; 
    } 
}; 

template<class T> 
struct Functor<T, typename std::is_reference<typename T::Vec&>::type> { 
    void operator()() const { 
    std::cerr << "special" << std::endl; 
    } 
}; 

struct Foo { 
    typedef std::vector<int> Vec; 
}; 

int main() { 
    Functor<Foo> ac; 
    ac(); 
} 

Questo funziona per qualsiasi tipo Vec che potrebbe dare un senso qui, compresi i tipi fondamentali e array, per esempio, e riferimenti o puntatori a loro.

1

Un'altra alternativa per rilevare l'esistenza di un tipo di membro è utilizzare void_t. Poiché le specializzazioni parziali valide sono preferibili all'implementazione generale, purché corrispondano ai parametri predefiniti, vogliamo un tipo che valuti a void quando è valido, ed è valido solo quando esiste il membro specificato; questo tipo è comunemente (e, come da C++ 17, canonicamente) noto come void_t.

template<class...> 
using void_t = void; 

Se il compilatore non supporta correttamente esso (nei primi mesi del C++ 14 compilatori, i parametri utilizzati nei modelli di alias non sono stati garantiti per garantire SFINAE, rompendo sopra void_t), una soluzione è disponibile.

template<typename... Ts> struct make_void { typedef void type; }; 
template<typename... Ts> using void_t = typename make_void<Ts...>::type; 

partire dal C++ 17, void_t è disponibile nella libreria utilities, in type_traits.

#include <iostream> 
#include <vector> 
#include <type_traits> // For void_t. 

template<class T, class V = void> 
struct Functor { 
    void operator()() const { 
    std::cerr << "general" << std::endl; 
    } 
}; 

// Use void_t here. 
template<class T> 
struct Functor<T, std::void_t<typename T::Vec>> { 
    void operator()() const { 
    std::cerr << "special" << std::endl; 
    } 
}; 

struct Foo { 
    typedef std::vector<int> Vec; 
}; 

int main() { 
    Functor<Foo> ac; 
    ac(); 
} 

Con questo, l'uscita è special, come previsto.


In questo caso, dal momento che stiamo verificando l'esistenza di un tipo di membro, il processo è molto semplice; può essere fatto senza espressione SFINAE o la libreria type_traits, consentendoci di riscrivere il controllo per utilizzare le strutture C++ 03 se necessario.

// void_t: 
// Place above Functor's definition. 
template<typename T> struct void_t { typedef void type; }; 

// ... 

template<class T> 
struct Functor<T, typename void_t<typename T::Vec>::type> { 
    void operator()() const { 
    std::cerr << "special" << std::endl; 
    } 
}; 

A mia conoscenza, questo dovrebbe funzionare sulla maggior parte, se non tutti, SFINAE-capable C++ 03-, C++ 11-, C++ 14-, o C++ 1Z conformi compilatori . Questo può essere utile quando si ha a che fare con compilatori che sono in ritardo rispetto allo standard un po ', o quando si compila per piattaforme che non hanno ancora compilatori compatibili con C++ 11.


Per ulteriori informazioni su void_t, vedere cppreference.

Problemi correlati