std :: enable_if per compilare condizionalmente una funzione membro


156

Sto cercando di ottenere un semplice esempio per capire come utilizzare std::enable_if. Dopo aver letto questa risposta , ho pensato che non sarebbe stato troppo difficile trovare un semplice esempio. Voglio usare std::enable_ifper scegliere tra due funzioni membro e consentire l'utilizzo solo di una di esse.

Sfortunatamente, quanto segue non viene compilato con gcc 4.7 e dopo ore e ore di tentativi, sto chiedendo a voi ragazzi qual è il mio errore.

#include <utility>
#include <iostream>

template< class T >
class Y {

    public:
        template < typename = typename std::enable_if< true >::type >
        T foo() {
            return 10;
        }
        template < typename = typename std::enable_if< false >::type >
        T foo() {
            return 10;
        }

};


int main() {
    Y< double > y;

    std::cout << y.foo() << std::endl;
}

gcc segnala i seguenti problemi:

% LANG=C make CXXFLAGS="-std=c++0x" enable_if
g++ -std=c++0x    enable_if.cpp   -o enable_if
enable_if.cpp:12:65: error: `type' in `struct std::enable_if<false>' does not name a type
enable_if.cpp:13:15: error: `template<class T> template<class> T Y::foo()' cannot be overloaded
enable_if.cpp:9:15: error: with `template<class T> template<class> T Y::foo()'

Perché g ++ non elimina l'istanza errata per la funzione del secondo membro? Secondo lo standard, std::enable_if< bool, T = void >::typeesiste solo quando il parametro del modello booleano è true. Ma perché g ++ non lo considera SFINAE? Penso che il messaggio di errore di sovraccarico provenga dal problema che g ++ non cancella la funzione del secondo membro e ritiene che questo dovrebbe essere un sovraccarico.


1
Non ne sono sicuro, ma penso che sia il seguente: enable_if si basa su SFINAE (l'errore di sostituzione non è un errore). Tuttavia, non si ha alcuna sostituzione qui, perché nessun parametro non può essere utilizzato per determinare quale sovraccarico utilizzare. Dovresti far dipendere il "vero" e il "falso" da T. (So che non volevi farlo nell'esempio semplice, ma probabilmente è troppo semplice ora ...)
Philipp

3
Ci ho pensato anche io e ho provato a usare std::is_same< T, int >::valuee ! std::is_same< T, int >::valueche dà lo stesso risultato.
evnu,

Risposte:


117

SFINAE funziona solo se la sostituzione nella deduzione dell'argomento di un argomento modello rende il costrutto mal formato. Non esiste tale sostituzione.

Ci ho pensato anche io e ho provato a usare std::is_same< T, int >::valuee ! std::is_same< T, int >::valueche dà lo stesso risultato.

Questo perché quando il modello di classe viene istanziato (cosa che accade quando si crea un oggetto di tipo Y<int>tra gli altri casi), crea un'istanza di tutte le dichiarazioni dei suoi membri (non necessariamente le loro definizioni / corpi!). Tra questi ci sono anche i suoi modelli di membri. Si noti che Tè noto allora e !std::is_same< T, int >::valueproduce falso. Quindi creerà una classe Y<int>che contiene

class Y<int> {
    public:
        /* instantiated from
        template < typename = typename std::enable_if< 
          std::is_same< T, int >::value >::type >
        T foo() {
            return 10;
        }
        */

        template < typename = typename std::enable_if< true >::type >
        int foo();

        /* instantiated from

        template < typename = typename std::enable_if< 
          ! std::is_same< T, int >::value >::type >
        T foo() {
            return 10;
        }
        */

        template < typename = typename std::enable_if< false >::type >
        int foo();
};

Il std::enable_if<false>::typeaccede a un inesistente tipo, in modo che la dichiarazione è mal-formata. E quindi il tuo programma non è valido.

Devi fare in modo che i modelli dei membri enable_ifdipendano da un parametro del modello membro stesso. Quindi le dichiarazioni sono valide, poiché l'intero tipo è ancora dipendente. Quando si tenta di chiamarne uno, si verifica la deduzione dell'argomento per gli argomenti del modello e SFINAE avviene come previsto. Vedi questa domanda e la risposta corrispondente su come farlo.


14
... Solo per chiarire, nel caso sia utile: quando Yviene istanziata un'istanza della classe template, il compilatore non compilerà effettivamente le funzioni del membro template; tuttavia, il compilatore eseguirà la sostituzione Tnel DICHIARAZIONE modello di membro in modo che tali modelli di membro possano essere istanziati in un secondo momento. Questo punto di errore non è SFINAE, poiché SFINAE si applica solo quando si determina l'insieme di possibili funzioni per la risoluzione del sovraccarico e l'istanza di una classe non è un caso per determinare un insieme di funzioni per la risoluzione del sovraccarico. (O
almeno

93

Ho fatto questo breve esempio che funziona anche.

#include <iostream>
#include <type_traits>

class foo;
class bar;

template<class T>
struct is_bar
{
    template<class Q = T>
    typename std::enable_if<std::is_same<Q, bar>::value, bool>::type check()
    {
        return true;
    }

    template<class Q = T>
    typename std::enable_if<!std::is_same<Q, bar>::value, bool>::type check()
    {
        return false;
    }
};

int main()
{
    is_bar<foo> foo_is_bar;
    is_bar<bar> bar_is_bar;
    if (!foo_is_bar.check() && bar_is_bar.check())
        std::cout << "It works!" << std::endl;

    return 0;
}

Commenta se vuoi che elabori. Penso che il codice sia più o meno autoesplicativo, ma poi l'ho fatto di nuovo, quindi potrei sbagliarmi :)

Puoi vederlo in azione qui .


2
Questo non viene compilato su VS2012. error C4519: default template arguments are only allowed on a class template.
PythonNut,

1
È un peccato. L'ho provato solo con gcc. Forse questo aiuta: stackoverflow.com/a/17543296/660982
jpihl

1
questa è sicuramente la risposta migliore qui ed esattamente quello che stavo cercando.
Weipeng L

3
Perché è necessario creare un'altra classe di modelli Q, anche se è uguale a T?
ilya1725,

1
Perché è necessario modellare la testfunzione membro. Entrambi non possono esistere contemporaneamente. Qinoltra solo il tipo di modello di classe T. È possibile rimuovere il modello di classe in questo Tmodo: cpp.sh/4nxw ma che in qualche modo sconfigge lo scopo.
jpihl,

13

Per i ritardatari che cercano una soluzione che "funzioni":

#include <utility>
#include <iostream>

template< typename T >
class Y {

    template< bool cond, typename U >
    using resolvedType  = typename std::enable_if< cond, U >::type; 

    public:
        template< typename U = T > 
        resolvedType< true, U > foo() {
            return 11;
        }
        template< typename U = T >
        resolvedType< false, U > foo() {
            return 12;
        }

};


int main() {
    Y< double > y;

    std::cout << y.foo() << std::endl;
}

Compilare con:

g++ -std=gnu++14 test.cpp 

La corsa dà:

./a.out 
11

6
Uhm, perché si rinomina std::enable_if_ta resolvedType.
Qwertie,

1
Perché non tutti possono usare C ++ 17 per ragioni che possono variare notevolmente.
James Yang,

9

Da questo post:

Gli argomenti del modello predefinito non fanno parte della firma di un modello

Ma si può fare qualcosa del genere:

#include <iostream>

struct Foo {
    template < class T,
               class std::enable_if < !std::is_integral<T>::value, int >::type = 0 >
    void f(const T& value)
    {
        std::cout << "Not int" << std::endl;
    }

    template<class T,
             class std::enable_if<std::is_integral<T>::value, int>::type = 0>
    void f(const T& value)
    {
        std::cout << "Int" << std::endl;
    }
};

int main()
{
    Foo foo;
    foo.f(1);
    foo.f(1.1);

    // Output:
    // Int
    // Not int
}

Funziona, ma in pratica si tratta di funzioni di modellamento, non della classe stessa ... Non consente nemmeno di abbandonare una delle due funzioni con prototipo identico (quando è necessario superare il sovraccarico). Tuttavia, l'idea è carina. Potresti riscrivere l'esempio OP in un modulo di lavoro, per favore?
user1284631

5

Un modo per risolvere questo problema, la specializzazione delle funzioni membro è di inserire la specializzazione in un'altra classe, quindi ereditare da quella classe. Potrebbe essere necessario modificare l'ordine di ereditarietà per ottenere l'accesso a tutti gli altri dati sottostanti, ma questa tecnica funziona.

template< class T, bool condition> struct FooImpl;
template<class T> struct FooImpl<T, true> {
T foo() { return 10; }
};

template<class T> struct FoolImpl<T,false> {
T foo() { return 5; }
};

template< class T >
class Y : public FooImpl<T, boost::is_integer<T> > // whatever your test is goes here.
{
public:
    typedef FooImpl<T, boost::is_integer<T> > inherited;

    // you will need to use "inherited::" if you want to name any of the 
    // members of those inherited classes.
};

Lo svantaggio di questa tecnica è che se hai bisogno di testare molte cose diverse per le diverse funzioni membro dovrai creare una classe per ognuna e incatenarla nella struttura ereditaria. Questo vale per l'accesso a membri di dati comuni.

Ex:

template<class T, bool condition> class Goo;
// repeat pattern above.

template<class T, bool condition>
class Foo<T, true> : public Goo<T, boost::test<T> > {
public:
    typedef Goo<T, boost::test<T> > inherited:
    // etc. etc.
};

4

Il valore booleano deve dipendere dal parametro template dedotto. Quindi un modo semplice per risolvere è usare un parametro booleano predefinito:

template< class T >
class Y {

    public:
        template < bool EnableBool = true, typename = typename std::enable_if<( std::is_same<T, double>::value && EnableBool )>::type >
        T foo() {
            return 10;
        }

};

Tuttavia, questo non funzionerà se si desidera sovraccaricare la funzione membro. Invece, è meglio usare TICK_MEMBER_REQUIRESdalla libreria Tick :

template< class T >
class Y {

    public:
        TICK_MEMBER_REQUIRES(std::is_same<T, double>::value)
        T foo() {
            return 10;
        }

        TICK_MEMBER_REQUIRES(!std::is_same<T, double>::value)
        T foo() {
            return 10;
        }

};

È inoltre possibile implementare il proprio membro richiede macro come questa (nel caso in cui non si desidera utilizzare un'altra libreria):

template<long N>
struct requires_enum
{
    enum class type
    {
        none,
        all       
    };
};


#define MEMBER_REQUIRES(...) \
typename requires_enum<__LINE__>::type PrivateRequiresEnum ## __LINE__ = requires_enum<__LINE__>::type::none, \
class=typename std::enable_if<((PrivateRequiresEnum ## __LINE__ == requires_enum<__LINE__>::type::none) && (__VA_ARGS__))>::type

Non ha funzionato per me in quel modo. Maaybe manca qualcosa? Potresti riscrivere l'esempio OP in un modulo di lavoro, per favore?
user1284631

L'esempio originale non funziona con il sovraccarico. Ho aggiornato la mia risposta su come puoi farlo con il sovraccarico.
Paul Fultz II,

0

Ecco il mio esempio minimalista, usando una macro. Usa parentesi doppie enable_if((...))quando usi espressioni più complesse.

template<bool b, std::enable_if_t<b, int> = 0>
using helper_enable_if = int;

#define enable_if(value) typename = helper_enable_if<value>

struct Test
{
     template<enable_if(false)>
     void run();
}
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.