Specializzazione parziale del modello di funzione C ++?


91

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

template <typename T1, typename T2> 
class MyClass { 
  … 
}; 


// partial specialization: both template parameters have same type 
template <typename T> 
class MyClass<T,T> { 
  … 
}; 

Inoltre so 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 per Microsoft Visual Studio 2010 Express! Se no, potresti spiegare il concetto di specializzazione parziale?

#include <iostream>
using std::cin;
using std::cout;
using std::endl;

template <typename T1, typename T2> 
inline T1 max (T1 const& a, T2 const& b) 
{ 
    return a < b ? b : a; 
} 

template <typename T> 
inline T const& max (T const& a, T const& b)
{
    return 10;
}


int main ()
{
    cout << max(4,4.2) << endl;
    cout << max(5,5) << endl;
    int z;
    cin>>z;
}

Cerca quell'analogia della specializzazione di classe. Se si chiama specializzazione di classe, perché dovrei considerare la stessa cosa per la funzione come sovraccarico?
Narek

1
No, la sintassi della specializzazione è diversa. Guarda la (presunta) sintassi della specializzazione della funzione nella mia risposta di seguito.
iammilind

2
Perché questo non genera un errore "La chiamata al massimo è ambigua"? Come si max(5,5)risolve max(T const&, T const&) [with T=int]e no max(T1 const&, T2 const&) [with T1=int and T2=int]?
NHDaly

Risposte:


83

La specializzazione parziale della funzione non è ancora consentita come da norma. Nell'esempio, stai effettivamente sovraccaricando e non specializzando la max<T1,T2>funzione.
La sua sintassi avrebbe dovuto assomigliare in qualche modo a quella seguente, se fosse stato consentito:

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

Nel caso di modelli di funzione, solo la specializzazione completa è consentita dallo standard C ++, - escluse le estensioni del compilatore!


1
@Narek, La specializzazione parziale delle funzioni non fa parte dello standard (per qualsiasi motivo). Penso che MSVC lo supporti come estensione. Potrebbe essere dopo un po 'di tempo, sarebbe consentito anche da altri compilatori.
iammilind

1
@iammilind: nessun problema. Sembra già saperlo. Questo è il motivo per cui lo sta provando anche per il modello di funzione. Quindi l'ho modificato di nuovo, rendendolo chiaro ora.
Nawaz

22
Chi sa spiegare perché la specializzazione parziale non è consentita?
HelloGoodbye

2
@NHDaly, non fornisce errori di ambiguità perché 1 funzione è migliore dell'altra. Perché seleziona(T, T) sopra (T1, T2)per (int, int), è perché la prima garantisce che ci sono 2 parametri ed entrambi i tipi sono gli stessi; quest'ultimo garantisce solo che ci siano 2 parametri. Il compilatore sceglie sempre una descrizione accurata. es. Se dovessi scegliere tra 2 descrizioni di un "fiume" quale sceglieresti? "raccolta dell'acqua" vs "raccolta dell'acqua che scorre".
iammilind

1
@kfsone, penso che questa funzione sia in fase di revisione, quindi aperta all'interpretazione. Puoi fare riferimento a questa sezione open-std , che ho visto in Perché lo standard C ++ non consente la specializzazione parziale del modello di funzione?
iammilind

44

Poiché la specializzazione parziale non è consentita - come indicato da altre risposte -, potresti aggirarla usando std::is_samee std::enable_if, come di seguito:

template <typename T, class F>
inline typename std::enable_if<std::is_same<T, int>::value, void>::type
typed_foo(const F& f) {
    std::cout << ">>> messing with ints! " << f << std::endl;
}

template <typename T, class F>
inline typename std::enable_if<std::is_same<T, float>::value, void>::type
typed_foo(const F& f) {
    std::cout << ">>> messing with floats! " << f << std::endl;
}

int main(int argc, char *argv[]) {
    typed_foo<int>("works");
    typed_foo<float>(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, è possibile aggiungere una definizione in cui si afferma che i casi già trattati non dovrebbero corrispondere , altrimenti si cadrà in definizioni ambigue. La definizione potrebbe essere:

template <typename T, class F>
inline typename std::enable_if<(not std::is_same<T, int>::value)
    and (not std::is_same<T, float>::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<int>("works");
    typed_foo<float>(2);
    typed_foo<std::string>("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 poche specializzazioni in più.


Non c'è davvero alcuna necessità di farlo poiché questo può essere gestito sovraccaricando le funzioni in modo molto più semplice e chiaro.
Adrian

2
@ Adrian Non riesco davvero a pensare a nessun altro approccio di sovraccarico di funzioni per risolvere questo problema. Hai notato che il sovraccarico parziale non è consentito, giusto? Condividi con noi la tua soluzione, se pensi che sia più chiara.
Rubens

1
c'è un altro modo per catturare facilmente tutte le funzioni basate su modelli?
Nick

15

Cos'è la specializzazione?

Se vuoi davvero capire i modelli, dovresti dare un'occhiata ai linguaggi funzionali. Il mondo dei modelli in C ++ è un sottolinguaggio puramente funzionale a sé stante.

Nei linguaggi funzionali, le selezioni vengono effettuate 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 classe C ++ funzionano esattamente allo stesso modo. Fornisci un main dichiarazione , che indica il numero e la natura dei parametri. Può essere solo una dichiarazione, o funge anche da definizione (a tua scelta), quindi puoi (se lo desideri) fornire specializzazioni del modello e associarle una versione diversa (altrimenti sarebbe sciocca) della classe .

Per le funzioni dei modelli, la specializzazione è un po 'più scomoda: è in qualche modo in conflitto con la risoluzione dell'overload. Pertanto, è stato deciso che una specializzazione si riferirebbe a una versione non specializzata e le specializzazioni non sarebbero state prese in considerazione durante la risoluzione del sovraccarico. Pertanto, l'algoritmo per la selezione della funzione giusta diventa:

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

(per un trattamento approfondito, vedere GotW # 49 )

In quanto tale, la specializzazione dei modelli delle funzioni è un cittadino di seconda zona (letteralmente). Per quanto mi riguarda, staremmo meglio senza di loro: devo ancora incontrare un caso in cui l'utilizzo di una specializzazione di template non possa essere risolto con il sovraccarico.

È una specializzazione del modello?

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 collegato).


"As such, template specialization of functions is a second-zone citizen (literally). As far as I am concerned, we would be better off without them: I have yet to encounter a case where a template specialization use could not be solved with overloading instead."Che ne dici di parametri di modello non di tipo?
Jules GM

@ Julius: puoi ancora usare il sovraccarico, anche se introducendo un parametro fittizio come boost::mpl::integral_c<unsigned, 3u>. Un'altra soluzione potrebbe essere quella di utilizzare enable_if/ disable_if, sebbene sia una storia diversa.
Matthieu M.

8

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

Tutti i problemi nell'informatica possono essere risolti con un altro livello di indirezione. —— David Wheeler

L'aggiunta di una classe per inoltrare la chiamata alla funzione può risolvere questo problema, ecco un esempio:

template <class Tag, class R, class... Ts>
struct enable_fun_partial_spec;

struct fun_tag {};

template <class R, class... Ts>
constexpr R fun(Ts&&... ts) {
  return enable_fun_partial_spec<fun_tag, R, Ts...>::call(
      std::forward<Ts>(ts)...);
}

template <class R, class... Ts>
struct enable_fun_partial_spec<fun_tag, R, Ts...> {
  constexpr static R call(Ts&&... ts) { return {0}; }
};

template <class R, class T>
struct enable_fun_partial_spec<fun_tag, R, T, T> {
  constexpr static R call(T, T) { return {1}; }
};

template <class R>
struct enable_fun_partial_spec<fun_tag, R, int, int> {
  constexpr static R call(int, int) { return {2}; }
};

template <class R>
struct enable_fun_partial_spec<fun_tag, R, int, char> {
  constexpr static R call(int, char) { return {3}; }
};

template <class R, class T2>
struct enable_fun_partial_spec<fun_tag, R, char, T2> {
  constexpr static R call(char, T2) { return {4}; }
};

static_assert(std::is_same_v<decltype(fun<int>(1, 1)), int>, "");
static_assert(fun<int>(1, 1) == 2, "");

static_assert(std::is_same_v<decltype(fun<char>(1, 1)), char>, "");
static_assert(fun<char>(1, 1) == 2, "");

static_assert(std::is_same_v<decltype(fun<long>(1L, 1L)), long>, "");
static_assert(fun<long>(1L, 1L) == 1, "");

static_assert(std::is_same_v<decltype(fun<double>(1L, 1L)), double>, "");
static_assert(fun<double>(1L, 1L) == 1, "");

static_assert(std::is_same_v<decltype(fun<int>(1u, 1)), int>, "");
static_assert(fun<int>(1u, 1) == 0, "");

static_assert(std::is_same_v<decltype(fun<char>(1, 'c')), char>, "");
static_assert(fun<char>(1, 'c') == 3, "");

static_assert(std::is_same_v<decltype(fun<unsigned>('c', 1)), unsigned>, "");
static_assert(fun<unsigned>('c', 1) == 4, "");

static_assert(std::is_same_v<decltype(fun<unsigned>(10.0, 1)), unsigned>, "");
static_assert(fun<unsigned>(10.0, 1) == 0, "");

static_assert(
    std::is_same_v<decltype(fun<double>(1, 2, 3, 'a', "bbb")), double>, "");
static_assert(fun<double>(1, 2, 3, 'a', "bbb") == 0, "");

static_assert(std::is_same_v<decltype(fun<unsigned>()), unsigned>, "");
static_assert(fun<unsigned>() == 0, "");

4

No. Ad esempio, puoi specializzarti legalmente std::swap, ma non puoi definire legalmente il tuo sovraccarico. Ciò significa che non puoi farestd::swap funzionare il tuo modello di classe personalizzato.

Il sovraccarico e la specializzazione parziale possono avere lo stesso effetto in alcuni casi, ma tutt'altro.


4
Ecco perché metti il ​​tuo swapsovraccarico nel tuo spazio dei nomi.
jpalecek

2

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

Quindi immaginiamo, questo è ciò che abbiamo cercato di risolvere:

template <typename R, typename X, typename Y>
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 <typename R, typename Y>
void function<R, int, Y>(int x, Y y) 
{
    // unfortunately, Wrapper has no constructor accepting int:
    Wrapper* w = new Wrapper();
    w->setValue(x);
    f(w, y);
}

OK, specializzazione parziale della funzione modello, non possiamo farlo ... Quindi "esportiamo" la parte necessaria per la specializzazione in una funzione di supporto, specializziamola e usiamola:

template <typename R, typename T>
R* create(T t)
{
    return new R(t);
}
template <>
Wrapper* create<Wrapper, int>(int n) // fully specialized now -> legal...
{
    Wrapper* w = new Wrapper();
    w->setValue(n);
    return w;
}

template <typename R, typename X, typename Y>
void function(X x, Y y)
{
    R* r = create<R>(x);
    f(r, y); // another template function?
}

Questo può essere interessante soprattutto se le alternative (normali sovraccarichi invece di specializzazioni, la soluzione alternativa proposta da Rubens, ... - non che queste siano cattive o la mia sia migliore, solo un'altra ) condividessero un bel po 'di codice comune.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.