2008-11-11 27 views
21

Chcę, aby ten specjalistyczny bez zmiany głównego. Czy można specjalizować coś na podstawie swojej klasy bazowej? Mam nadzieję.Specjalizacja szablonów w oparciu o dziedziczenie klasy

-edit-

będę miał kilka klas, które dziedziczą z SomeTag. Nie chcę pisać tej samej specjalizacji dla każdego z nich.

class SomeTag {}; 
class InheritSomeTag : public SomeTag {}; 

template <class T, class Tag=T> 
struct MyClass 
{ 
}; 

template <class T> 
struct MyClass<T, SomeTag> 
{ 
    typedef int isSpecialized; 
}; 

int main() 
{ 
    MyClass<SomeTag>::isSpecialized test1; //ok 
    MyClass<InheritSomeTag>::isSpecialized test2; //how do i make this specialized w/o changing main() 
    return 0; 
} 

Odpowiedz

23

Ten artykuł opisuje zgrabną sztuczkę: http://www.gotw.ca/publications/mxc++-item-4.htm

Oto podstawowy pomysł. Trzeba najpierw klasę IsDerivedFrom (ta zapewnia czas pracy i czasie kompilacji sprawdzanie):

template<typename D, typename B> 
class IsDerivedFrom 
{ 
    class No { }; 
    class Yes { No no[3]; }; 

    static Yes Test(B*); // not defined 
    static No Test(...); // not defined 

    static void Constraints(D* p) { B* pb = p; pb = p; } 

public: 
    enum { Is = sizeof(Test(static_cast<D*>(0))) == sizeof(Yes) }; 

    IsDerivedFrom() { void(*p)(D*) = Constraints; } 
}; 

Wtedy twój MojaKlasa wymaga implementacji, która jest potencjalnie specjalistyczne:

template<typename T, int> 
class MyClassImpl 
{ 
    // general case: T is not derived from SomeTag 
}; 

template<typename T> 
class MyClassImpl<T, 1> 
{ 
    // T is derived from SomeTag 
    public: 
    typedef int isSpecialized; 
}; 

i MojaKlasa faktycznie wygląda:

template<typename T> 
class MyClass: public MyClassImpl<T, IsDerivedFrom<T, SomeTag>::Is> 
{ 
}; 

W takim przypadku główne urządzenie będzie w porządku, tak jak jest:

int main() 
{ 
    MyClass<SomeTag>::isSpecialized test1; //ok 
    MyClass<InheritSomeTag>::isSpecialized test2; //ok also 
    return 0; 
} 
+0

Umysł boggles! Domyślam się, że nieokreśloną funkcją 'No' jest ..... – John

+0

Piękna. Mam tylko jedno, naprawdę głupie pytanie - czy istnieje powód, że "klasa Tak" jest zadeklarowana jako "Nie nie [3]" zamiast, powiedzmy, "Nie nie [2]"? Przypuszczam, że to też zadziała, ale może brakuje mi czegoś ważnego ... –

3

W twoim przypadku, to jedyny sposób, że widzę byłoby wyraźnie specjalizują MyClass dla InheritSomeTag. Jednak SeqAn paper proponuje mechanizm o nazwie "sublasowanie szablonów", który robi to, co chcesz - ale z inną składnią dziedziczenia, więc kod nie jest zgodny z bieżącą funkcją main.

// Base class 
template <typename TSpec = void> 
class SomeTag { }; 

// Type tag, NOT part of the inheritance chain 
template <typename TSpec = void> 
struct InheritSomeTag { }; 

// Derived class, uses type tag 
template <typename TSpec> 
class SomeTag<InheritSomeTag<TSpec> > : public SomeTag<void> { }; 

template <class T, class Tag=T> 
struct MyClass { }; 

template <class T, typename TSpec> 
struct MyClass<T, SomeTag<TSpec> > 
{ 
    typedef int isSpecialized; 
}; 

int main() 
{ 
    MyClass<SomeTag<> >::isSpecialized test1; //ok 
    MyClass<SomeTag<InheritSomeTag<> > >::isSpecialized test2; //ok 
} 

To z pewnością wygląda dziwnie i jest bardzo uciążliwe, ale pozwala prawdziwy mechanizm dziedziczenia z polimorficznych funkcji, który jest wykonywany w czasie kompilacji. Jeśli chcesz zobaczyć to w akcji, spójrz na niektóre SeqAn examples.

Mając na uwadze powyższe, uważam, że SeqAn jest szczególnym przypadkiem i nie wiele aplikacji będzie zysk z tej niezwykle trudnej składni (rozszyfrowanie błędów związanych SeqAn-kompilator jest prawdziwy ból * SS!)

19

Cóż, artykuł w powyższej odpowiedzi pojawił się w lutym 2002 roku. Mimo że działa, dziś wiemy, że istnieją lepsze sposoby. Alternatywnie, można użyć enable_if:

template<bool C, typename T = void> 
struct enable_if { 
    typedef T type; 
}; 

template<typename T> 
struct enable_if<false, T> { }; 

template<typename, typename> 
struct is_same { 
    static bool const value = false; 
}; 

template<typename A> 
struct is_same<A, A> { 
    static bool const value = true; 
}; 

template<typename B, typename D>         
struct is_base_of {              
    static D * create_d();      
    static char (& chk(B *))[1]; 
    static char (& chk(...))[2];   
    static bool const value = sizeof chk(create_d()) == 1 && 
           !is_same<B volatile const, 
             void volatile const>::value; 
}; 

struct SomeTag { }; 
struct InheritSomeTag : SomeTag { }; 

template<typename T, typename = void> 
struct MyClass { /* T not derived from SomeTag */ }; 

template<typename T> 
struct MyClass<T, typename enable_if<is_base_of<SomeTag, T>::value>::type> { 
    typedef int isSpecialized; 
}; 

int main() { 
    MyClass<SomeTag>::isSpecialized test1;  /* ok */ 
    MyClass<InheritSomeTag>::isSpecialized test2; /* ok */ 
} 
+1

Człowieku, zupełnie zapomniałem o SFINAE, kiedy napisałem odpowiedź. Mimo to pozwalam mu pokazać dziwną alternatywę. –

+0

@litb: Chociaż zdefiniowanie struktur "enable_if" i "is_base_of" jest zbędne, jeśli można użyć TR1; ale dzięki za to, że pomogłeś mi zrozumieć :) – legends2k

+0

'boost' również ma wbudowaną tę funkcję: [enable_if] (http://www.boost.org/doc/libs/1_47_0/libs/utility/enable_if.html) i [is_base_of] (http://www.boost.org/doc/libs/1_41_0/libs/type_traits/doc/html/boost_typetraits/reference/is_base_of.html).(Przydało mi się to, ponieważ dowiedziałem się, że już używam doładowania w moim pakiecie, może będzie to również przydatne dla kogoś innego). –

14

a skróconą wersję teraz 2014, używając C++ - 11:

#include <type_traits> 

struct SomeTag { }; 
struct InheritSomeTag : SomeTag { }; 

template<typename T, bool = std::is_base_of<SomeTag, T>::value> 
struct MyClass { }; 

template<typename T> 
struct MyClass<T, true> { 
    typedef int isSpecialized; 
}; 

int main() { 
    MyClass<SomeTag>::isSpecialized test1;  /* ok */ 
    MyClass<InheritSomeTag>::isSpecialized test2; /* ok */ 
} 
Powiązane problemy