Specializzazione parziale del modello di funzione C ++?

So che il codice seguente è una specializzazione parziale di una class:

template  class MyClass { … }; // partial specialization: both template parameters have same type template  class MyClass { … }; 

So anche che C ++ non consente la specializzazione parziale del modello di funzione (è consentito solo il pieno). Ma il mio codice significa che ho parzialmente specializzato il mio modello di funzione per argomenti dello stesso tipo? Perché funziona con Microsoft Visual Studio 2010 Express! Se no, allora potresti spiegare il concetto di specializzazione parziale?

 #include  using std::cin; using std::cout; using std::endl; template  inline T1 max (T1 const& a, T2 const& b) { return a < b ? b : a; } template  inline T const& max (T const& a, T const& b) { return 10; } int main () { cout << max(4,4.2) << endl;; cout << max(5,5) <>z; } 

Nell’esempio, si sta effettivamente sovraccaricando (non specializzando) la funzione max . La syntax di specializzazione parziale avrebbe dovuto apparire alquanto simile ( se fosse stato permesso ):

 //Partial specialization is not allowed by the spec, though! template  inline T const& max (T const& a, T const& b) { ^^^^^ <--- specializing here return 10; } 

[Nota: nel caso di un modello di funzione, solo la specializzazione completa è consentita dallo standard C ++ (escluse le estensioni del compilatore).]

Poiché la specializzazione parziale non è consentita, come indicato da altre risposte, è ansible aggirare il problema utilizzando std::is_same e std::enable_if , come di seguito:

 template  inline typename std::enable_if::value, void>::type typed_foo(const F& f) { std::cout << ">>> messing with ints! " << f << std::endl; } template  inline typename std::enable_if::value, void>::type typed_foo(const F& f) { std::cout << ">>> messing with floats! " << f << std::endl; } int main(int argc, char *argv[]) { typed_foo("works"); typed_foo(2); } 

Produzione:

 $ ./a.out >>> messing with ints! works >>> messing with floats! 2 

Modifica : nel caso in cui sia necessario essere in grado di trattare tutti gli altri casi rimasti, è ansible aggiungere una definizione in cui si afferma che i casi già trattati non devono corrispondere , altrimenti ci si ritroverà in definizioni ambigue. La definizione potrebbe essere:

 template  inline typename std::enable_if<(not std::is_same::value) and (not std::is_same::value), void>::type typed_foo(const F& f) { std::cout << ">>> messing with unknown stuff! " << f << std::endl; } int main(int argc, char *argv[]) { typed_foo("works"); typed_foo(2); typed_foo("either"); } 

Che produce:

 $ ./a.out >>> messing with ints! works >>> messing with floats! 2 >>> messing with unknown stuff! either 

Anche se questa cosa di tutti i casi sembra un po ‘noiosa, dal momento che devi dire al compilatore tutto ciò che hai già fatto, è abbastanza fattibile trattare fino a 5 o più specializzazioni.

Cos’è la specializzazione?

Se vuoi veramente capire i modelli, dovresti dare un’occhiata ai linguaggi funzionali. Il mondo dei template in C ++ è un sottolinguaggio puramente funzionale.

Nei linguaggi funzionali, le selezioni vengono eseguite utilizzando Pattern Matching :

 -- An instance of Maybe is either nothing (None) or something (Just a) -- where a is any type data Maybe a = None | Just a -- declare function isJust, which takes a Maybe -- and checks whether it's None or Just isJust :: Maybe a -> Bool -- definition: two cases (_ is a wildcard) isJust None = False isJust Just _ = True 

Come puoi vedere, sovraccarichiamo la definizione di isJust .

Bene, i modelli di class C ++ funzionano esattamente allo stesso modo. Fornisci una dichiarazione principale , che indica il numero e la natura dei parametri. Può essere solo una dichiarazione, o anche una definizione (a tua scelta), e quindi puoi (se lo desideri) fornire delle specializzazioni del modello e associarle a una versione diversa (altrimenti sarebbe sciocca) della class .

Per le funzioni dei modelli, la specializzazione è un po ‘più scomoda: è in qualche modo in conflitto con la risoluzione del sovraccarico. Come tale, è stato deciso che una specializzazione si riferirebbe a una versione non specializzata, e le specializzazioni non sarebbero considerate durante la risoluzione di sovraccarico. Pertanto, l’algoritmo per la selezione della funzione corretta diventa:

  1. Esegue la risoluzione di sovraccarico, tra funzioni regolari e modelli non specializzati
  2. Se è selezionato un modello non specializzato, controlla se esiste una specializzazione per esso che sarebbe una corrispondenza migliore

(per un trattamento approfondito, vedi GotW # 49 )

In quanto tale, la specializzazione del modello delle funzioni è un cittadino di seconda zona (letteralmente). Per quanto mi riguarda, staremmo meglio senza di loro: devo ancora incontrare un caso in cui un uso della specializzazione del modello non può essere risolto con sovraccarico.

Si tratta di una specializzazione template?

No, è semplicemente un sovraccarico, e questo va bene. In effetti, i sovraccarichi di solito funzionano come ci aspettiamo, mentre le specializzazioni possono essere sorprendenti (ricorda l’articolo di GotW che ho linkato).

No. Ad esempio, puoi specializzare legalmente std::swap , ma non puoi definire legalmente il tuo sovraccarico. Ciò significa che non puoi eseguire std::swap work per il tuo modello di class personalizzato.

Il sovraccarico e la specializzazione parziale possono avere lo stesso effetto in alcuni casi, ma lontano da tutti.

Non è consentita la specializzazione parziale non variabile, non variabile, ma come detto:

Tutti i problemi in informatica possono essere risolti da un altro livello di riferimento indiretto. – David Wheeler

Aggiungere una class per inoltrare la chiamata di funzione può risolvere questo, ecco un esempio:

 template  struct enable_fun_partial_spec; struct fun_tag {}; template  constexpr R fun(Ts&&... ts) { return enable_fun_partial_spec::call( std::forward(ts)...); } template  struct enable_fun_partial_spec { constexpr static R call(Ts&&... ts) { return {0}; } }; template  struct enable_fun_partial_spec { constexpr static R call(T, T) { return {1}; } }; template  struct enable_fun_partial_spec { constexpr static R call(int, int) { return {2}; } }; template  struct enable_fun_partial_spec { constexpr static R call(int, char) { return {3}; } }; template  struct enable_fun_partial_spec { constexpr static R call(char, T2) { return {4}; } }; static_assert(std::is_same_v(1, 1)), int>, ""); static_assert(fun(1, 1) == 2, ""); static_assert(std::is_same_v(1, 1)), char>, ""); static_assert(fun(1, 1) == 2, ""); static_assert(std::is_same_v(1L, 1L)), long>, ""); static_assert(fun(1L, 1L) == 1, ""); static_assert(std::is_same_v(1L, 1L)), double>, ""); static_assert(fun(1L, 1L) == 1, ""); static_assert(std::is_same_v(1u, 1)), int>, ""); static_assert(fun(1u, 1) == 0, ""); static_assert(std::is_same_v(1, 'c')), char>, ""); static_assert(fun(1, 'c') == 3, ""); static_assert(std::is_same_v('c', 1)), unsigned>, ""); static_assert(fun('c', 1) == 4, ""); static_assert(std::is_same_v(10.0, 1)), unsigned>, ""); static_assert(fun(10.0, 1) == 0, ""); static_assert( std::is_same_v(1, 2, 3, 'a', "bbb")), double>, ""); static_assert(fun(1, 2, 3, 'a', "bbb") == 0, ""); static_assert(std::is_same_v()), unsigned>, ""); static_assert(fun() == 0, ""); 

Risposta tardiva, ma alcuni lettori in ritardo potrebbero trovarlo utile: a volte, una funzione di supporto – progettata in modo tale che possa essere specializzata – può risolvere anche il problema.

Quindi immaginiamo, questo è quello che abbiamo cercato di risolvere:

 template  void function(X x, Y y) { R* r = new R(x); f(r, y); // another template function? } // for some reason, we NEED the specialization: template  void function(int x, Y y) { // unfortunately, Wrapper has no constructor accepting int: Wrapper* w = new Wrapper(); w->setValue(x); f(w, y); } 

OK, parziale specializzazione della funzione template, non possiamo farlo … Quindi, esportiamo la parte necessaria per la specializzazione in una funzione helper, specializzacanvas e usacanvas:

 template  R* create(T t) { return new R(t); } template <> Wrapper* create(int n) // fully specialized now -> legal... { Wrapper* w = new Wrapper(); w->setValue(n); return w; } template  void function(X x, Y y) { R* r = create(x); f(r, y); // another template function? } 

Questo può essere interessante specialmente se le alternative (sovraccarico normale al posto di specializzazioni, soluzione alternativa proposta da Rubens, … – non che queste siano cattive o che la mia sia migliore, solo un’altra ) condividono un bel po ‘di codice comune.