Riformulando la citazione cppreference, nel caso sbagliato abbiamo:
typename = std::enable_if_t<std::is_integral<Integer>::value>
typename = std::enable_if_t<std::is_floating_point<Floating>::value>
che sono entrambi argomenti del modello predefinito e non fanno parte della firma del modello di funzione. Quindi nel caso sbagliato si ottengono due identici firme.
Nel caso giusto:
typename std::enable_if_t<std::is_integral<Integer>::value, int> = 0
e
typename std::enable_if_t<std::is_floating_point<Floating>::value, int> = 0
non hai più argomenti di modello predefiniti, ma due tipi diversi con valore predefinito (=0). Quindi le firme sono diverse
Aggiorna dal commento :per chiarire la differenza,
Un esempio con parametro modello con tipo predefinito:
template<typename T=int>
void foo() {};
// usage
foo<double>();
foo<>();
Un esempio con parametro modello non di tipo con valore predefinito
template<int = 0>
void foo() {};
// usage
foo<4>();
foo<>();
Un'ultima cosa che può confondere nel tuo esempio è l'utilizzo di enable_if_t
, infatti nel tuo caso codice hai un superfluo typename
:
template <
typename Integer,
typename std::enable_if_t<std::is_integral<Integer>::value, int> = 0
>
T(Integer) : m_type(int_t) {}
sarebbe meglio scrivere come:
template <
typename Floating,
std::enable_if_t<std::is_floating_point<Floating>::value, int> = 0
>
(lo stesso vale per la seconda dichiarazione).
Questo è precisamente il ruolo di enable_if_t
:
template< bool B, class T = void >
using enable_if_t = typename enable_if<B,T>::type;
per non dover aggiungere typename
(rispetto al vecchio enable_if
)
Principalmente perché [temp.over.link]/6 non parla dell'argomento predefinito del modello:
Quindi da [temp.over.link]/7:
... i due modelli nel tuo primo esempio sono equivalenti, mentre i due modelli nel tuo secondo esempio non lo sono. Quindi i due modelli nel tuo primo esempio dichiarano la stessa entità e risultano in un costrutto mal formato da [class.mem]/5:
La prima versione è sbagliata nello stesso modo in cui questo snippet è sbagliato:
template<int=7>
void f();
template<int=8>
void f();
Il motivo non ha nulla a che fare con l'errore di sostituzione:la sostituzione avviene solo quando i modelli di funzione vengono utilizzati (ad es. in una funzione invocazione ), ma le mere dichiarazioni sono sufficienti per attivare l'errore di compilazione.
La dicitura standard pertinente è [dcl.fct.default]:
La seconda versione è corretta perché i modelli di funzione hanno una firma diversa e quindi non vengono trattati come la stessa entità dal compilatore.