Approcci per il funzionamento di SFINAE in C ++


40

Sto usando pesantemente la funzione SFINAE in un progetto e non sono sicuro che ci siano differenze tra i seguenti due approcci (diversi dallo stile):

#include <cstdlib>
#include <type_traits>
#include <iostream>

template <class T, class = std::enable_if_t<std::is_same_v<T, int>>>
void foo()
{
    std::cout << "method 1" << std::endl;
}

template <class T, std::enable_if_t<std::is_same_v<T, double>>* = 0>
void foo()
{
    std::cout << "method 2" << std::endl;
}

int main()
{
    foo<int>();
    foo<double>();

    std::cout << "Done...";
    std::getchar();

    return EXIT_SUCCESS;
}

L'output del programma è come previsto:

method 1
method 2
Done...

Ho visto il metodo 2 usato più spesso in StackOverflow, ma preferisco il metodo 1.

Ci sono circostanze in cui questi due approcci differiscono?


Come si esegue questo programma? Non si compila per me.
alter igel,

@alter igel avrà bisogno di un compilatore C ++ 17. Ho usato MSVC 2019 per testare questo esempio, ma lavoro principalmente con Clang.
keith

Correlati: why-should-i-Avoid-stdenable-if-in-function-signature e C ++ 20 introduce anche nuovi modi con il concetto :-)
Jarod42

@ Jarod42 I concetti sono una delle cose più necessarie per me da C ++ 20.
dice Val Reinstate Monica il

Risposte:


35

Ho visto il metodo 2 usato più spesso in StackOverflow, ma preferisco il metodo 1.

Suggerimento: preferisci il metodo 2.

Entrambi i metodi funzionano con singole funzioni. Il problema sorge quando si dispone di più di una funzione, con la stessa firma e si desidera abilitare solo una funzione dell'insieme.

Supponiamo che tu voglia abilitare foo(), versione 1, quando bar<T>()(fingi che sia una constexprfunzione) truee foo(), versione 2, quando lo bar<T>()è false.

Con

template <typename T, typename = std::enable_if_t<true == bar<T>()>>
void foo () // version 1
 { }

template <typename T, typename = std::enable_if_t<false == bar<T>()>>
void foo () // version 2
 { }

ricevi un errore di compilazione perché hai un'ambiguità: due foo()funzioni con la stessa firma (un parametro di modello predefinito non cambia la firma).

Ma la seguente soluzione

template <typename T, std::enable_if_t<true == bar<T>(), bool> = true>
void foo () // version 1
 { }

template <typename T, std::enable_if_t<false == bar<T>(), bool> = true>
void foo () // version 2
 { }

funziona perché SFINAE modifica la firma delle funzioni.

Osservazione non correlata: esiste anche un terzo metodo: abilitare / disabilitare il tipo restituito (tranne per i costruttori di classe / struttura, ovviamente)

template <typename T>
std::enable_if_t<true == bar<T>()> foo () // version 1
 { }

template <typename T>
std::enable_if_t<false == bar<T>()> foo () // version 2
 { }

Come metodo 2, il metodo 3 è compatibile con la selezione di funzioni alternative con la stessa firma.


1
Grazie per l'ottima spiegazione, preferirò i metodi 2 e 3 d'ora in poi :-)
keith

"un parametro di modello predefinito non cambia la firma" - come è diverso nella seconda variante, che utilizza anche i parametri di modello predefiniti?
Eric,

1
@Eric - Non è semplice dirlo ... Suppongo che l'altra risposta spieghi meglio questo ... Se SFINAE abilita / disabilita l'argomento template predefinito, la foo()funzione rimane disponibile quando la chiami con un secondo parametro template esplicito (la foo<double, double>();chiamata). E se rimangono disponibili, c'è un'ambiguità con l'altra versione. Con il metodo 2, SFINAE abilita / disabilita il secondo argomento, non il parametro predefinito. Quindi non puoi chiamarlo spiegando il parametro perché c'è un errore di sostituzione che non consente un secondo parametro. Quindi la versione non è disponibile, quindi nessuna ambiguità
max66

3
Il metodo 3 ha l'ulteriore vantaggio di non perdere in genere il nome-simbolo. La variante auto foo() -> std::enable_if_t<...>è spesso utile per evitare di nascondere la firma della funzione e per consentire l'utilizzo degli argomenti della funzione.
Deduplicatore il

@ max66: quindi il punto chiave è che l'errore di sostituzione in un parametro predefinito del modello non è un errore se il parametro viene fornito e non è necessario alcun valore predefinito?
Eric,

21

Oltre alla risposta di max66 , un altro motivo per preferire il metodo 2 è che con il metodo 1, è possibile (accidentalmente) passare un parametro di tipo esplicito come secondo argomento del modello e sconfiggere completamente il meccanismo SFINAE. Questo potrebbe accadere come errore di battitura, copia / incolla o come svista in un meccanismo modello più grande.

#include <cstdlib>
#include <type_traits>
#include <iostream>

// NOTE: foo should only accept T=int
template <class T, class = std::enable_if_t<std::is_same_v<T, int>>>
void foo(){
    std::cout << "method 1" << std::endl;
}

int main(){

    // works fine
    foo<int>();

    // ERROR: subsitution failure, as expected
    // foo<double>();

    // Oops! also works, even though T != int :(
    foo<double, double>();

    return 0;
}

Demo live qui


Buon punto. Il meccanismo può essere dirottato.
max66,
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.