2012-05-15 15 views
6

Questo problema è un po 'difficile da spiegare, quindi inizierà con un esempio:specializzazione Modello per sottoclassi di classe base template

Ho un modello di classe che prende un tipo e una costante intera come parametri di modello, e ho un certo numero di classi figlie che derivano da istanze di quel modello:

template <class V, int i> 
struct Base 
{ 
    static void doSomething() { cout << "something " << i << endl; }; 
}; 

struct Child : public Base<int,12> 
{ 
}; 

voglio usare queste classi con qualche altro modello (chiamiamolo test), che ha specializzazioni per i diversi tipi. Poiché il comportamento dovrebbe essere esattamente lo stesso per tutte le classi derivate da qualsiasi istanziazione di Base, voglio definire solo una singola specializzazione di Test che gestisca tutte le classi derivate da Base.

So che non posso specializzarmi direttamente per Base < V, i > perché questo non rileverebbe le classi figlio. Invece, il mio primo approccio è stato utilizzato enable_if e tipo tratti di Boost:

// empty body to trigger compiler error for unsupported types 
template <class T, class Enabled = void> 
struct Test { }; 

// specialization for ints, 
// in my actual code, I have many more specializations here 
template <class Enabled> 
struct Test <int, Enabled> 
{ 
    static void test (int dst) 
    { 
     cout << "Test<int>::test(" << dst << ")" << endl; 
    } 
}; 

// this should handle all subclasses of Base, 
// but it doesn't compile 
template <class T, class V, int i> 
struct Test <T, typename enable_if <is_base_and_derived <Base <V,i>, T>>::type> 
{ 
    static void test (const T &dst) 
    { 
     dst.doSomething(); 
    } 
}; 

int main (int argc, char **argv) 
{ 
    Test <int>::test (23); 
    Test <Child>::test (Child()); 
    return 0; 
} 

L'idea era che la specializzazione deve gestire tutte le classi che sono derivati ​​da base con tutti i valori arbitrari di V e i. Questo non funziona, gcc si lamenta:

 
error: template parameters not used in partial specialization: 
error:   ‘V’ 
error:   ‘i’ 

Credo che il problema è che questo approccio richiederebbe il compilatore per provare tutte le possibili combinazioni di V ed I per verificare se qualcuno di loro partite. Per il momento, ho lavorato tutto il problema aggiungendo qualcosa alla classe di base:

template <class V, int i> 
struct Base 
{ 
    typedef V VV; 
    static constexpr int ii = i; 
    static void doSomething() { cout << "something " << i << endl; }; 
}; 

In questo modo, la specializzazione non ha più bisogno di avere V e I parametri di modello liberi:

template <class T> 
struct Test <T, typename enable_if <is_base_and_derived <Base <typename T::VV, T::ii>, T>>::type> 
{ 
    static void test (const T &dst) 
    { 
     dst.doSomething(); 
    } 
}; 

E poi compila.

Ora, la mia domanda è: Come posso fare questo senza modificare la classe base? In questo caso è stato possibile perché l'ho scritto da solo, ma cosa posso fare se devo gestire il codice di libreria di terze parti nel mio modello di test in questo modo? C'è una soluzione più elegante?

Modifica: Inoltre, qualcuno può darmi una spiegazione dettagliata perché esattamente il primo approccio non funziona? Ho un'idea approssimativa, ma preferirei avere una comprensione adeguata. :-)

risposta

3

una soluzione semplice è quella di lasciare Base eredita un'altra Base_base:

struct Base_base 
{}; 

template <class V, int i> 
struct Base 
: public Base_base 
{ 
    static void doSomething() { cout << "something " << i << endl; }; 
}; 

template <class T> 
struct Test <T, typename enable_if <is_base_and_derived <Base_base, T>>::type> 
{ 
    static void test (const T &dst) 
    { 
     dst.doSomething(); 
    } 
}; 

[Edited] in un codice di terze parti, è possibile utilizzare un trucco simile:

template <class V, int i> 
struct Base3rdparty 
{ 
    static void doSomething() { cout << "something " << i << endl; }; 
}; 

template <class V, int i> 
struct Base 
: public Base3rdparty<V, i> 
{ 
    typedef V VV; 
    static constexpr int ii = i; 
}; 

template <class T> 
struct Test <T, typename enable_if <is_base_and_derived <Base <typename T::VV, T::ii>, T>>::type> 
{ 
    static void test (const T &dst) 
    { 
     dst.doSomething(); 
    } 
}; 
+0

Grazie per il suggerimento Base_Base, questo rende il mio codice corrente un po 'più leggibile. Tuttavia, per le librerie di terze parti, questo non funzionerà, almeno non se le classi figlie appartengono anche alla libreria. –

+1

@BenjaminSchug: forse [questo] (http://stackoverflow.com/a/6398983/1324131) risponde alla tua nuova domanda in una domanda modificata. – user2k5

+0

Grazie, questo spiega perché il primo approccio non ha funzionato.Sembra che il mio istinto fosse corretto. –

0

Usa sovraccarico di funzione e decltype:

// Never defined: 
template<typename T> std::false_type is_Base(T&); 
template<class V, int I> std::true_type is_Base(Base<V,I>&); 

template<typename IsBase, typename T> struct TestHelper; 
template<typename T> struct TestHelper<std::true_type, T> 
{ 
    static void test(const T& dst) { dst.doSomething(); } 
}; 
template<> struct TestHelper<std::false_type, int> 
{ 
    static void test(int dst) 
    { std::cout << "Test<int>::test(" << dst << ")" << std::endl; } 
}; 
// ... 

template<typename T> struct Test 
{ 
    static void test(const T& dst) 
    { TestHelper<decltype(is_Base(std::declval<T&>())), T>::test(dst); } 
} 
Problemi correlati