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.