2009-08-26 13 views
15

Ho il seguente problema:priorità nella scelta di funzioni modello sovraccarico in C++

class Base 
{ 
}; 

class Derived : public Base 
{ 
}; 

class Different 
{ 
}; 

class X 
{ 
public: 
    template <typename T> 
    static const char *func(T *data) 
    { 
    // Do something generic... 
    return "Generic"; 
    } 

    static const char *func(Base *data) 
    { 
    // Do something specific... 
    return "Specific"; 
    } 
}; 

Se ora faccio

Derived derived; 
Different different; 
std::cout << "Derived: " << X::func(&derived) << std::endl; 
std::cout << "Different: " << X::func(&different) << std::endl; 

ottengo

Derived: Generic 
Different: Generic 

Ma quello che voglio è che per tutte le classi derivate da Base viene chiamato il metodo specifico. Così il risultato dovrebbe essere:

Derived: Specific 
Different: Generic 

C'è un modo per ridisegnare il X: func (...) s per raggiungere questo obiettivo?

EDIT:

Si supponga che non è conosciuto dal chiamante di X :: func (...) se la classe presentata come parametro è derivata dalla base oppure no. Quindi, Casting to Base non è un'opzione. In realtà l'idea alla base di tutto è che X :: func (...) dovrebbe 'rilevare' se il parametro è derivato da Base o no e chiama un codice diverso. E per motivi di prestazioni il 'rilevamento' dovrebbe essere fatto in fase di compilazione.

risposta

14

Ho trovato una soluzione MOLTO facile!

class Base 
{ 
}; 

class Derived : public Base 
{ 
}; 

class Different 
{ 
}; 

class X 
{ 
private: 
    template <typename T> 
    static const char *intFunc(const void *, T *data) 
    { 
    // Do something generic... 
    return "Generic"; 
    } 

    template <typename T> 
    static const char *intFunc(const Base *, T *data) 
    { 
    // Do something specific... 
    return "Specific"; 
    } 

public: 
    template <typename T> 
    static const char *func(T *data) 
    { 
    return intFunc(data, data); 
    } 
}; 

Questo funziona benissimo ed è molto sottile! Il trucco è lasciare che il compilatore selezioni il metodo corretto con il primo parametro (altrimenti inutile).

+1

+1, avrei dovuto pensarci. Questo trucco è in realtà utilizzato molto in AWL per selezionare tra varie versioni di algoritmi basati sulla categoria degli iteratori passati. – avakar

+1

Tenere a mente la versione Boost, tuttavia, funzionerà con altri tratti rispetto alla convertibilità. – avakar

+0

grazie per la pubblicazione, è davvero una bella soluzione. – ttvd

0

Proprio typecast derivato basare

X :: func ((Base *) & derivato)

funziona ....

+3

In C++, è preferibile utilizzare static_cast o dynamic_cast nei cast in stile C. (c.f. Stevens) –

1

L'espressione:

X::func(derived) 

Means che il compilatore genererà una dichiarazione e un codice che ha effettivamente questa firma:

static const char *func(Derived *data); 

che si rivela essere una partita migliore del tuo:

static const char *func(Base *data); 

La funzione di modello verrà utilizzato per tutto ciò che è legale per typename, per esempio qualsiasi classe tu usi come T e in effetti escluderà la versione Base della tua funzione dall'uso, a causa della politica del tempo di compilazione.

Il mio suggerimento è quello di utilizzare specialization in X per i tipi specifici, cioè .:

template <typename T> 
    static const char *func(T *data) 
    { 
    // Do something generic... 
    return "Generic"; 
    } 

template <> 
    static const char *func(Derived *data) // 'Derived' is the specific type 
    { 
    // Do something specific... 
    return "Specific"; 
    } 

Speranza che funziona!

+0

No, non funziona. – avakar

+0

No, non funziona. Ho appena provato con gcc. Ha senso dal momento che si ottiene ancora una corrispondenza migliore con la versione generica. – Troubadour

+0

Whoops - pensato per specializzarsi come stat const char * func (Dati derivati ​​*) ... questi due dovrebbero almeno essere uguali negli occhi del compilatore. Spero che scelga quello esplicitamente specializzato ... – jscharf

7

Per questo è necessario utilizzare SFINAE. Nel codice seguente, la prima funzione può essere istanziata se e solo se si passa qualcosa che non può essere (implicitamente) convertito in Base *. La seconda funzione ha questo rovesciato.

Si potrebbe leggere su enable_if.

#include <iostream> 
#include <boost/utility/enable_if.hpp> 
#include <boost/type_traits.hpp> 

class Base {}; 
class Derived : public Base {}; 
class Different {}; 

struct X 
{ 
    template <typename T> 
    static typename boost::disable_if<boost::is_convertible<T *, Base *>, 
     const char *>::type func(T *data) 
    { 
     return "Generic"; 
    } 

    template <typename T> 
    static typename boost::enable_if<boost::is_convertible<T *, Base *>, 
     const char *>::type func(T *data) 
    { 
     return "Specific"; 
    } 
}; 

int main() 
{ 
    Derived derived; 
    Different different; 
    std::cout << "Derived: " << X::func(&derived) << std::endl; 
    std::cout << "Different: " << X::func(&different) << std::endl; 
} 
+0

Bene, 'enable_if' è abbastanza facile da implementare. Tuttavia, 'is_convertible' sembra essere una storia abbastanza diversa. Personalmente, non vorrei implementarlo da solo. – avakar

+0

Precisamente. Tuttavia, molti di essi sembrano essere soluzioni alternative per vari compilatori non conformi. Forse c'è una versione breve ed elegante nascosta in quel file da qualche parte. Non ho davvero voglia di cercarlo, e non sto scommettendo nemmeno tu. Vorrei andare con Boost :-) – avakar

+0

Tuttavia, si noti che ci possono essere due nozioni: convertibilità puntatore e convertibilità universale.La seguente convertibilità del puntatore test: Fondamentalmente, è: 'template struct is_ptr_convertible {static char (& is (B *)) [1]; static char (& is (...)) [2]; valore bool const statico = (sizeof is ((D *) 0) == 1); }; ' –

1

Se si utilizza spinta, è possibile farlo con un certo modello metaprogrammazione:

#include <boost/type_traits/is_base_of.hpp> 

class X 
{ 
private: 
    template <typename T> 
    static const char *generic_func(T *data) 
    { 
     // Do something generic... 
     return "Generic"; 
    } 

    template <typename T> 
    static const char *base_func(T *data) 
    { 
     // Do something specific... 
     return "Specific"; 
    } 

public: 
    template <typename T> 
    static const char* func(T* data) 
    { 
     if (boost::is_base_of<Base, T>::value) 
      return base_func(data); 

     return generic_func(data); 
    } 
}; 

Il is_base_of metafunction viene valutata al momento della compilazione e l'ottimizzatore molto probabilmente rimuovere il ramo morto del if nella funzione func. Questo approccio ti consente di avere più di un caso specifico.

0

Stavo cercando di impostare le priorità sulle sovrapposizioni di enable_if, in particolare per ricadere sui metodi di contenitore STL, dove i miei tratti erano cose come is_assignable is_insterable ecc. Con cui si sovrappone su un numero di contenitori.

Desidero assegnare la priorità all'assegnazione, se esistente, altrimenti utilizzare un iteratore di inserimento. Questo è un esempio generico di ciò che mi è venuto in mente (modificato con infiniti livelli di priorità da alcune persone a portata di mano nel canale #boost irc). Funziona come la conversione implicita del livello di priorità classifica il sovraccarico al di sotto di un altro che è altrimenti un'opzione ugualmente valida - rimuovendo l'ambiguità.

#include <iostream> 
#include <string> 

template <std::size_t N> 
struct priority : priority<N - 1> {}; 

template <> 
struct priority<0> {}; 

using priority_tag = priority<2>; 

template <typename T> 
void somefunc(T x, priority<0>) 
{ 
    std::cout << "Any" << std::endl; 
} 

template <typename T> 
std::enable_if_t<std::is_pod<T>::value > 
somefunc(T x, priority<2>) 
{ 
    std::cout << "is_pod" << std::endl; 
} 

template <typename T> 
std::enable_if_t<std::is_floating_point<T>::value > 
somefunc(T x, priority<1>) 
{ 
    std::cout << "is_float" << std::endl; 
} 

int main() 
{ 
    float x = 1; 
    somefunc(x, priority_tag{}); 
    int y = 1; 
    somefunc(y, priority_tag{}); 
    std::string z; 
    somefunc(z, priority_tag{}); 
    return 0; 
} 

E 'stato anche suggerito che in C++ 14 potessi usare constexpr se le dichiarazioni per ottenere la stessa cosa, che era molto più pulita se Visual Studio 2015 li hanno sostenuti. Speriamo che questo possa aiutare qualcun altro.

#include <iostream> 
#include <string> 

template <typename T> 
void somefunc(T x) 
{ 
    if constexpr(std::is_floating_point<T>::value) { 
     static_assert(std::is_floating_point<T>::value); 
     std::cout << "is_float" << std::endl; 
    } else if constexpr(std::is_pod<T>::value) { 
     static_assert(std::is_pod<T>::value); 
     std::cout << "is_pod" << std::endl; 
    } else { 
     static_assert(!std::is_floating_point<T>::value); 
     static_assert(!std::is_pod<T>::value); 
     std::cout << "Any" << std::endl; 
    } 
} 

int main() 
{ 
    float x = 1; 
    somefunc(x); 
    int y = 1; 
    somefunc(y); 
    std::string z; 
    somefunc(z); 
    return 0; 
} 

// grazie a k-ballo @ #boost!

Problemi correlati