Selezione di una funzione membro che utilizza diversi enable_if condizioni

Sto cercando di determinare quale versione di una funzione membro viene chiamato in base alla classe di parametro del modello. Ho provato questo:

#include <iostream>
#include <type_traits>

template<typename T>
struct Point
{
  void MyFunction(typename std::enable_if<std::is_same<T, int>::value, T >::type* = 0)
  {
    std::cout << "T is int." << std::endl;
  }

  void MyFunction(typename std::enable_if<!std::is_same<T, int>::value, float >::type* = 0)
  {
    std::cout << "T is not int." << std::endl;
  }
};

int main()
{
  Point<int> intPoint;
  intPoint.MyFunction();

  Point<float> floatPoint;
  floatPoint.MyFunction();
}

che ho pensato è dire “uso il primo MyFunction se T è di tipo int, e la seconda MyFunction se T non è di tipo int, ma mi da errori di compilazione dicendo “errore: nessun tipo di nome ‘tipo’ a ‘struct std::enable_if’”. Qualcuno può indicare che cosa sto facendo di sbagliato?

  • Correlati Q&A: “che Cosa è successo al mio SFINAE” (redux)
  • Aggiornamento: C++20 permetterà template<typename T> struct Point { void MyFunction() requires (std::is_same_v<T, int>); void MyFunction() requires (!std::is_same_v<T, int>); };. Qui è bene che l’espressione del vincolo non è dipendente e falso che solo rende l’intera funzione preferito in meno durante la risoluzione dell’overload.

 

5 Replies
  1. 13

    enable_if funziona perché il sostituzione di un modello argomento ha provocato un errore, e in modo che la sostituzione è sceso dal sovraccarico risoluzione impostata e solo con altra sovraccarichi sono considerati dal compilatore.

    Nel tuo esempio, non vi è alcuna sostituzione che si verificano quando si crea un’istanza funzioni membro, perché l’argomento del modello T è già noto a quel tempo. Il modo più semplice per ottenere ciò che stai cercando è quello di creare un modello fittizio argomento che di default T e l’uso che per eseguire SFINAE.

    template<typename T>
    struct Point
    {
      template<typename U = T>
      typename std::enable_if<std::is_same<U, int>::value>::type
        MyFunction()
      {
        std::cout << "T is int." << std::endl;
      }
    
      template<typename U = T>
      typename std::enable_if<std::is_same<U, float>::value>::type
        MyFunction()
      {
        std::cout << "T is not int." << std::endl;
      }
    };

    Edit:

    Come HostileFork menziona nei commenti, l’esempio originale lascia la possibilità per l’utente di specificare in modo esplicito argomenti di modello per le funzioni membro e ottenere un risultato non corretto. Il seguente dovrebbe evitare che esplicita le specializzazioni delle funzioni di membro di compilazione.

    template<typename T>
    struct Point
    {
      template<typename... Dummy, typename U = T>
      typename std::enable_if<std::is_same<U, int>::value>::type
        MyFunction()
      {
        static_assert(sizeof...(Dummy)==0, "Do not specify template arguments!");
        std::cout << "T is int." << std::endl;
      }
    
      template<typename... Dummy, typename U = T>
      typename std::enable_if<std::is_same<U, float>::value>::type
        MyFunction()
      {
        static_assert(sizeof...(Dummy)==0, "Do not specify template arguments!");
        std::cout << "T is not int." << std::endl;
      }
    };
    • In C++11, SFINAE regole è stato modificato un po’, a causa della quale SFINAE non attiverà sul tipo di ritorno. In breve, questa risposta è sbagliata.
    • Funziona bene su gcc4.7.2, non è possibile postare un link demo dal sito LWS è giù. Qui ideone demo.
    • Che non è una dimostrazione di C++11-conforme codice.
    • per me funziona su 4.7.2 così.
    • questo dovrebbe essere valido, giusto? modello<typename T> struct Punto { template<typename U = T> void Miafunzione(typename std::enable_if<std::is_same<U, int>::valore U >::tipo* = 0) { std::cout << “T int”. << std::endl; } template<typename U = T> void Miafunzione(typename std::enable_if<!std::is_same<U, int>::valore, float >::tipo* = 0) { std::cout << “T int.” << std::endl; } };
    • …inoltre c’è niente di arresto da uno a fare un mix e match, se qualcuno esplicitamente specializzata e non utilizzare l’impostazione predefinita. Così si arriva a situazioni come intPoint.MyFunction<float>() quali sono probabilmente corrette. Statico affermare nel corpo, che garantisce una T abbinati dello stesso tipo si è verificato U contro è necessario pure. :-/
    • Ho fatto una nuova risposta, che utilizza il tuo suggerimento, ma non si muove il SFINAE trigger per il tipo di ritorno (per evitare le voci di c++11 che Nawaz citato). Pensieri?
    • Buon punto, ha aggiunto un altro esempio che utilizza static_assert per evitare esplicito specializzazioni dei modelli di membro.
    • Non so se credere Nawaz che C++11 cambiato le regole e si è rotto una tonnellata di enable_if codice di là fuori. L’esempio nella tua risposta dovrebbe lavorare troppo; io preferisco il modello fittizio argomento in contrapposizione al manichino argomento della funzione, ma è solo una preferenza personale. Inoltre, ho postato un altro esempio che utilizza static_assert per evitare che l’utente esplicitamente specializzato stati modello.
    • Non mi piace il manichino argomento della funzione, ma anche io non voglio scrivere il codice che sta per rompere con i prossimi gcc 🙂 (come Nawaz suggerisce che può). C’è un modo per utilizzare un modello fittizio argomento e hanno un tipo di ritorno void? Inoltre, qual è il … chiamato in questo contesto, così posso controllare?
    • Presumo si intende utilizzare il modello fittizio argomento e di ritorno sono di tipo altri di void. Il secondo, facoltativo, modello argomento per enable_if è il tipo di argomento, in modo da utilizzare typename enable_if<expr, SomeType>::type, e il tipo risultante sarà SomeType. Il ... la risposta è variadic modelli, un altro C++11 funzione.
    • Ah vedo, non avevo capito che c’era un valore predefinito di “void” come secondo enable_if parametro. Quindi, con la variabile ” numero di parametri del modello, non funziona come per le funzioni in cui la “fissa” parametri di andare per primo e poi gli “altri argomenti” sono passati a “…” (cioè hai Ciuccio… prima di T=U).
    • Dove fa esattamente lo standard C++11 dicono che si può fare più? Sarebbe una bella audace modifica di rilievo, IMO, e non vedo perché dovrebbero farlo. Anche se fosse vero, la (ora) idiomatiche template<class T> auto f(T& v) -> decltype(v.foo()); SFINAE costruire, che verifica la presenza di un membro, non avrebbe funzionato.
    • Confronta i vocaboli di $14.8.2/2, C++03 con la formulazione di $14.8.2/8 dal C++11. Secondo il C++11, questo non sembra essere SFINAE, piuttosto si tratta di un errore. Vedere questo argomento : stackoverflow.com/questions/12015938/…
    • Che non implicano in alcun modo che la sostituzione di un tipo di ritorno non è un soft-errore più. Il problema nell’altro thread che all’interno di di meta<int>, si ottiene un errore, che non accade mai con enable_if.

  2. 2

    Una soluzione semplice è quello di utilizzare la delega al lavoratore privato funzioni:

    template<typename T>
    struct Point
    {
    
      void MyFunction()
      {
         worker(static_cast<T*>(0)); //pass null argument of type T*
      }
    
    private:
    
      void worker(int*)
      {
        std::cout << "T is int." << std::endl;
      }
    
      template<typename U>
      void worker(U*)
      {
        std::cout << "T is not int." << std::endl;
      }
    };

    Quando T è int, il primo worker verrà chiamata la funzione, perché static_cast<T*>(0) risulta essere di tipo int*. In tutti gli altri casi, la versione del modello di lavoratore sarà chiamato.

    • Io in realtà come questa. Non credo che avrei mai usarlo, ma l’OP esempio è così contorto che questa è davvero una buona soluzione.
    • static_cast<U>(nullptr)
  3. 1

    enable_if funziona solo per dedotto funzione del modello di argomenti o per classe specializzata modello di argomenti. Quello che stai facendo non funziona, perché, ovviamente, con un fisso T = int, la seconda dichiarazione è semplicemente errata.

    Questo è come si può fare:

    template <typename T>
    void MyFreeFunction(Point<T> const & p,
                        typename std::enable_if<std::is_same<T, int>::value>::type * = NULL)
    {
        std::cout << "T is int" << std::endl;
    }
    
    //etc.
    
    int main()
    {
        Point<int> ip;
        MyFreeFunction(ip);
    }

    Un’alternativa sarebbe specializzati Point per vari tipi di T, o di mettere al di sopra di una funzione in un membro annidato wrapper di modello (che è probabilmente la più “giusta” soluzione).

    • Ho visto questa soluzione, ma sembra davvero danneggiare la leggibilità del codice.
    • Il codice originale è troppo artificiosa per rendere più adattato suggerimento.
    • Se il tuo caso è che si sta utilizzando solo SFINAE per verificare se alcuni tipi sono is_same (quindi con un default se non della partita) modello-specializzata Point per coloro che fissa i tipi di esattamente ciò che si voleva. Si avrebbe la stessa istanza con più leggibile definizioni.
  4. 1

    Base Pretorio suggerimento (ma senza cambiare il tipo di ritorno della funzione), questo sembra funzionare:

    #include <iostream>
    #include <type_traits>
    
    template<typename T>
    struct Point
    {
      template<typename U = T>
      void MyFunction(typename std::enable_if<std::is_same<U, int>::value, U >::type* = 0)
      {
        std::cout << "T is int." << std::endl;
      }
    
      template<typename U = T>
      void MyFunction(typename std::enable_if<!std::is_same<U, int>::value, float >::type* = 0)
      {
        std::cout << "T is not int." << std::endl;
      }
    };
    
    int main()
    {
      Point<int> intPoint;
      intPoint.MyFunction();
    
      Point<float> floatPoint;
      floatPoint.MyFunction();
    }
  5. 0

    Punto modello qui sotto è possibile creare un’istanza solo con int o float come argomento del modello T.

    Per rispondere alla domanda: qui lavoratore() viene chiamato esattamente a seconda del modello di parametro del metodo() la chiamata, ma ancora si è in controllo dei tipi.

        template<typename T>
        struct Point
        {
            static_assert (
                  std::is_same<T, int>()  ||
                  std::is_same<T, float>()
                );
    
            template<typename U>
            void method(U x_, U y_)
            {
                if constexpr (std::is_same<T, U>()) {
                    worker(x_, y_);
                    return;
                }
                //else 
                worker(
                    static_cast<T>(x_),
                    static_cast<T>(y_)
                );
                return ;
            }
    
    
        private:
    
            mutable T x{}, y{};
    
            void worker(T x_, T y_)
            {
                //nothing but T x, T y
            }
    
        };

    Sopra lavoratore() funziona anche se viene dichiarata come statico. Per qualche motivo valido. Poche altre estensioni di cui sopra sono possibili (e semplice), ma cerchiamo di bastone per la risposta.

Lascia un commento