2015-10-09 12 views
12

Mam klasy abstrakcyjnej dla porównywalnych + hashable wartości:C++ meta-programowania: Parametr szablonu, który * musi * dziedziczyć klasę abstrakcyjną

class Key 
{ 
public: 
    virtual bool operator ==(const Key&) const = 0; 
    virtual bool operator !=(const Key&) const = 0; 
    virtual u32 hashcode() const = 0; 
}; 

i trochę betonu klasy C, która dziedziczy to.

class C : public Key 
{ 
private: 
    u32 a, b; 
public: 
    static const C& null; // a prototype for representing a "no value" C 
    // Some reasonable implementation; it's just a pair 
    // ... 
}; 

i chciałbym wdrożyć szablonie klasy Hashset:

template<class T inherits Key, const T& proto> class HashSet 
{ 
    //... 
}; 

T jest typem wartości przechowywanych w tych zestawach. proto powinno być instancją T, która jest używana jako "zerowa" wartość typu T dla celów włączenia włączenia. Mam dość doświadczenie z C++, ale nie szczególnie z TMP i chociaż wydaje mi się, że coś, co powinno być krępujące i proste do zrobienia, nie mogę się zorientować, jak coś takiego jak mój pseudo-kod "klasa T dziedziczy klucz" jest w rzeczywistości Sporządzono w C++. Chcę być w stanie stworzyć hash-zestaw wystąpień C jak:

HashSet<C, C::null> myset; 

Czy ktoś mógłby mi powiedzieć, co właściwe i idiomatyczne sposób obsłużyć tę sytuację w C++ będzie? Dziękuję Ci!

+3

['std :: enable_if'] (http://en.cppreference.com/w/cpp/types/enable_if) i [' std :: is_base_of'] (http://en.cppreference.com/ w/cpp/types/is_base_of). – Biffen

+3

Nigdy nie zrozumiem, dlaczego "std :: enable_if" ukryty na liście szablonów szablonu podstawowego jest preferowany do 'static_assert' z przyjaznym dla użytkownika komunikatem –

+2

@PiotrSkotnicki Powiedziałbym, że to kompromis pomiędzy ładniejszym komunikat o błędzie i mające ograniczenia typu udokumentowane bezpośrednio w deklaracji, a nie ukryte w definicji. – TartanLlama

Odpowiedz

11

Można użyć std::enable_if_t i std::is_base_of na to:

template<class T, const T& proto, 
     std::enable_if_t<std::is_base_of<Key,T>::value>* = nullptr> 
class HashSet 
{ 
    //... 
}; 

Teraz HashSet dawałaby są ważne tylko wtedy T dziedziczy z Key.

std::enable_if_t to funkcja C++ 14. Jeśli utkniesz w C++ 11, możesz użyć typename std::enable_if<...>::type.

Live Demo


Innym rozwiązaniem byłoby wykorzystanie static_assert:

template<class T, const T& proto> 
class HashSet 
{ 
    static_assert(std::is_base_of<Key, T>::value, "T must inherit from Key"); 
}; 

To może nieco jaśniejsze i daje bardziej przyjazny komunikat o błędzie, ale twój typ ograniczenia nie podano w deklaracji klasowej.


Z Concepts Dostaniemy przejrzystość, lepsze komunikaty o błędach i zachować nasze ograniczenia w deklaracji:

template <class Base, class Derived>                                                   
concept bool IsBaseOf = std::is_base_of<Base, Derived>::value; 

template<class T, const T& proto> 
requires IsBaseOf<Key,T> 
class HashSet 
{}; 
+0

Wow, to jest obrzydliwe "ulepszenie" w C++ 14 ... To oczywiście nie jest komentarz do twojej odpowiedzi. Doceniam to rozwiązanie. Dzięki! – Thomas

+0

Tak, to jest okropne. Jak zauważył Piotr w komentarzach, można użyć 'static_assert' w definicji klasy, aby uzyskać nieco klarowności i ładniejszy komunikat o błędzie kosztem posiadania ograniczeń typu udokumentowanych w deklaracji. – TartanLlama

+4

@Thomas - to sposób C++. Spisek złowieszczej kabały, by zmienić język w okrucieństwo, aby stopa jego przynależności była niska, a płace - wysokie;) – dtech

2

Czy ktoś mógłby mi powiedzieć, co właściwe i idiomatyczne sposób obsłużyć tę sytuację w C++ byłoby?

Po prostu nie radzę sobie z tym. Jeśli użytkownik przejdzie w typ pochodzący od Key, instancja szablonu zadziała, nawet jeśli nie dodasz tego jako wyraźnego wymogu w adnotacji kodu. Jeśli użytkownik poda niepoprawny argument szablonu, to oczekuje się, że rzeczy się zepsują.

Następna wersja C++ będzie prawdopodobnie obsługiwała wyraźnie obejmujące takie adnotacje, ale w obecnej wersji C++, podczas gdy istnieją pewne sztuczki, których możesz użyć, z wyjątkiem sytuacji ograniczonych, idiomatyczny sposób polega na tym, aby po prostu nie zawracać sobie tym głowy. .

+1

Powiedziałbym, że sztuczki typu 'std :: enable_if' stały się de facto sposobem wyrażania ograniczeń typu zarówno dla użytkownika, jak i kompilatora, ale myślę, że jest to dyskusyjne. – TartanLlama

+0

@TartanLlama Tak, zgadzam się z tym, że gdy wyrażenia typu są wyrażone, to 'std :: enable_if' jest idiomatycznym sposobem robienia tego, ale ponieważ jest to rodzaj hackowania, wyrażanie ograniczeń typu jest czymś, co po prostu nie jest Zwykle to zrobione, przynajmniej nie z tego, co widziałem. :) – hvd

+0

Dzięki hvd. Właściwie nawet nie zdawałem sobie sprawy, że nie będę narzekać, dopóki nie wytworzę szablonu z T nie wspierając metod, których użyłem. Sądzę, że to nie jest wystarczająco inteligentne, by to sprawdzić, ani wystarczająco inteligentne, by pozwolić mi to zrobić elegancko, więc ... może to się wyrównuje. Zajmę się tymi rozwiązaniami i sprawdzę. Bardzo doceniane! – Thomas

Powiązane problemy