In che modo la modifica di un argomento modello da un tipo a un non tipo fa funzionare SFINAE?

In che modo la modifica di un argomento modello da un tipo a un non tipo fa funzionare SFINAE?

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.