Domanda

Ciao Sto avendo problemi selezionando la versione corretta di una classe template che ha una specializzazione esplicita. Sono voler selezionare una specializzazione utilizzando una classe derivata della classe utilizzata per specializzarsi. Lo scenario è il seguente:

#include <stdio.h>

class A
{};

class B: public A
{};

template<typename T>
class Foo
{
public:
   int FooBar(void) { return 10; }
};

// Explicit specialization for A
template<> int Foo< A >::FooBar( void ) { return 20; }

void main( void)
{
   Foo<B> fooB;

   // This prints out 10 instead of wanted 20 ie compiler selects the general version
   printf("%d", fooB.FooBar() );
}

Come dico nei miei commenti lì che voglio vedere 20 in fase di stampa fuori perché B è derivato da A, ma 10 viene stampato fuori invece. Come posso fare per ottenere la specializzazione denominata senza ricorrere alla scrittura di una specializzazione per ogni classe derivata (il mio scenario attuale ha molto di tipi derivati).

È stato utile?

Soluzione

--- EDIT: nuova risposta Facciamo l'originale approccio più gestibile. Tutte le scelte importanti si trovano nella definizione di Foo. Si suppone che sia facile da mantenere.

#include <boost/mpl/if.hpp>
#include  <boost/type_traits/is_base_of.hpp>
#include <iostream>

class A
{};

class B: public A
{};

class C{};
class D : public C{};
class E{};

struct DefaultMethod
{
    static int fooBar() { return 10; }
};
struct Method1
{
    static int fooBar() { return 20; }
};
struct Method2
{
    static int fooBar() { return 30; }
};

template<typename T, typename BaseClass, typename Choice1, typename OtherChoice>
struct IfDerivesFrom :
    boost::mpl::if_<
        typename boost::is_base_of<BaseClass, T>::type,
        Choice1,
        OtherChoice>::type
{
};

template<typename T>
struct Foo :
    IfDerivesFrom<T, A,
      Method1,
      IfDerivesFrom<T, C,
          Method2,
          DefaultMethod>
      >
{
};

int main()
{
    std::cout << Foo<A>::fooBar() << std::endl;
    std::cout << Foo<B>::fooBar() << std::endl;
    std::cout << Foo<C>::fooBar() << std::endl;
    std::cout << Foo<D>::fooBar() << std::endl;
    std::cout << Foo<E>::fooBar() << std::endl;

    return 0;
}

--- RISPOSTA ORIGINALE Se è possibile utilizzare boost, si può fare qualcosa di simile al seguente:

#include  <boost/type_traits/is_base_of.hpp>

template<bool b>
class FooHelper
{
    int FooBar();
};
template<> FooHelper<true>::FooBar(){ return 20;}
template<> FooHelper<false>::FooBar(){ return 10;}

template<typename T>
class Foo
{
public:
   int FooBar(void) { return FooHelper<boost::is_base_of<A, T>::type::value>(); }
};

Altri suggerimenti

Più in generale, si tratta di un problema di lunga data con il modello e l'eredità in generale.

Il problema è che il lavoro modello sul tipo esatto e non considerano il fattore ereditarietà, i due concetti essendo un po 'ortogonale, e quindi provare a mescolare uno e l'altro è spesso soggetto a errori.

Si potrebbe anche verificare fuori con metodi:

template <class T>
int fooBar(T) { return 10; }

int fooBar(A) { return 20; }

B b;
fooBar(b); // this returns 10, because fooBar<T> is a better match (no conversion)

Ora, ai suoi problemi, mentre apprezzo le varie soluzioni che sono state date utilizzando enable_if e is_base_of trucchi, io scartarli come non essere pratico. Il punto di una specializzazione è che l'autore di Foo non ha bisogno di sapere su come qualcuno sta per specializzarsi sua classe, se necessario, solo per rendere più facile. In caso contrario, se avete bisogno di una dozzina di specializzazione, si finisce con una classe Foo molto molto strano, questo è sicuro.

Lo STL ha già affrontato problemi simili. Il linguaggio accettato di solito è quello di fornire una classe tratti. La classe tratti predefiniti fornisce una buona soluzione per tutti, mentre ci si può specializzare una classe tratti per ospitare la propria necessità.

Penso che ci dovrebbe essere un modo utilizzando concetti (per esempio, se T definisce T :: pippo () e poi lo usa, altrimenti usa la versione di default ...), ma per la specifica del metodo di sovraccarico questo non è richiesto.

namespace detail { int fooBar(...) { return 10; } }

template <class T>
class Foo
{
public:
  static int FooBar() { T* t(0); return ::detail::fooBar(t); }
};

E ora, a specializzarsi per le classi derivate di A:

namespace detail { int fooBar(A*) { return 20; } }

Come funziona? Quando si considera sovraccarichi, i puntini di sospensione è l'ultimo metodo considerato, in modo che qualsiasi che si qualifica prima lo farà, quindi è perfetto per un comportamento di default.

Alcune considerazioni:

  • namespace: a seconda che scendessimo la fooBar identificatore è suscettibile di essere utilizzato o meno, si può preferire di isolare in uno spazio dei nomi del proprio (o dedicati alla classe Foo), in caso contrario, effettuare una chiamata non qualificata e lasciare l'utente definirlo nello spazio dei nomi della sua classe.

  • questo trucco funziona solo per l'eredità e la chiamata di metodo, non funziona se si desidera portare in typedef speciali

  • è possibile passare più modelli per il metodo effettivo, come il tipo reale

Ecco un esempio con le funzioni template

namespace detail { template <class T> int fooBar(...) { return 10; } }

template <class T>
int Foo<T>::FooBar() { T* t(0); return ::detail::fooBar<T>(t); }

namespace detail {
  template <class T>
  int fooBar(A*)
  {
    return T::FooBar();
  }
}

Ed ecco cosa accadrà:

struct None {};
struct A { static int FooBar() { return 20; } };
struct B: A {};
struct C: A { static int FooBar() { return 30; } };

int main(int argc, char* argv[])
{
  std::cout << Foo<None>::FooBar()  // prints 10
     << " " << Foo<A>::FooBar()     // prints 20
     << " " << Foo<B>::FooBar()     // prints 20
     << " " << Foo<C>::FooBar()     // prints 30
     << std::endl;
}

Per prima (minore) punto: il titolo non è corretto; questo è specializzazione esplicita, non la specializzazione parziale. Per ottenere la specializzazione parziale, è necessario specificare almeno un parametro di template, ma lasciare almeno un altro non specificato:

template <class T, class U>
demo { };

template <class T>
demo<int> {};  // use in the case of demo<XXX, int> 

Guardando il codice, sono un po 'sorpreso che si compila affatto. Non sono sicuro che ci sia alcun modo si potrebbe forzare la funzione specializzata di essere chiamato. Normalmente, ci si specializzano la classe nel suo insieme:

template<typename T>
class Foo
{
public:
   int FooBar(void) { return 10; }
};

template<>
class Foo<A> {
public:
    int FooBar() { return 20; }
};

In questo caso, che non sarà davvero fare nulla di buono però. È possibile convertire un oggetto derivato a un oggetto di base in modo implicito, ma è ancora una conversione. D'altra parte, la versione non-specializzato del modello può essere utilizzato con non di conversione - e quando picking quale usare, il compilatore considera uno che può essere un'istanza senza alcuna conversione come una migliore scelta di uno che richiede una conversione implicita.

Ecco una soluzione, ma non è particolarmente bello anche se:

template<typename T>
class Foo
{
public:
  int FooBar(typename disable_if<boost::is_base_of<A,T> >::type* dummy = 0) { return 10; }
  int FooBar(typename enable_if<boost::is_base_of<A,T> >::type* dummy = 0) { return 20; }
};

È necessario specializzarsi sul tipo esatto. Per esempio Foo<A> fooA; fooA.FooBar(); otterrebbe la 20. Oppure utilizzare boost.type_traits come @ Benoît mostra.

In sostanza, si vuole avere un trigger modello di specializzazione sulle classi derivate.

Se non avete bisogno la classe reale per essere specializzato, solo la funzione (s), sembra che si può solo fare:

int foo::foobar(A &someA);

Se poi bisogno di avere in realtà la classe essere specializzato, penso che si desidera guardare interfacce e il modello di dati di classe privata; più o meno, l'interfaccia "riduce" un oggetto a un modello specializzazione tipo riconosciuto, e quindi chiama attraverso; ala

int foo::foobar(A &someA)
{ return fooImpl<A>::foobar(someA); }

Ma suppongo che in realtà non risponde alla tua domanda, perché non supporta il caso generico. Suppongo si potrebbe avere:

template<class T>
class foo
{
public: 
    int foobar(T &t);
    int foobar(A &a);
}

foo<A>::foobar(someA);
foo<F>::foobar(someA);
foo<not B>::foobar(someB); //Should trigger foobar(A &a), right?

Questo sarebbe quindi in grado di riconoscere B come derivato da A, pur fornendo il caso generico. Penso; Non ho ancora testato questo.

Non è la più bella, ma penso di avere qualche opportunità per un certo divertimento roba controllo degli accessi simile in là, se si sta specializzandosi la classe attuale, come si può includere o non includere vari pippo (A & a) funzioni come per consentire o negare l'utilizzo su vari alberi di ereditarietà; per l'esempio precedente;

foo<C>::foobar(someF); //doesn't exist!
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top