Ogranicz parametr szablonu C ++ do podklasy


80

Jak mogę wymusić, Taby parametr szablonu był podklasą określonej klasy Baseclass? Coś takiego:

template <class T : Baseclass> void function(){
    T *object = new T();

}

3
Co próbujesz przez to osiągnąć?
sth

2
Chcę się tylko upewnić, że T jest faktycznie wystąpieniem podklasy lub samą klasą. Kod wewnątrz funkcji, którą podałem, jest prawie nieistotny.
phant0m

6
wręcz przeciwnie, jest to bardzo istotne. Określa, czy włożenie pracy w ten test jest dobrym pomysłem. W wielu (wszystkich?) Przypadkach nie ma absolutnie potrzeby samodzielnego wymuszania takich ograniczeń, ale raczej pozwól kompilatorowi zrobić to podczas tworzenia instancji. Na przykład dla zaakceptowanej odpowiedzi dobrze byłoby sprawdzić, czy Tpochodzi z Baseclass. W tej chwili to sprawdzenie jest niejawne i nie jest widoczne w celu rozpoznania przeciążenia. Ale jeśli nigdzie nie ma takiego domniemanego ograniczenia, wydaje się, że nie ma powodu do sztucznego ograniczenia.
Johannes Schaub - litb

1
Tak, zgadzam się. Jednak chciałem tylko wiedzieć, czy jest sposób, aby to osiągnąć, czy nie :) Ale oczywiście masz bardzo ważny punkt i dziękuję za wgląd.
phant0m

Odpowiedzi:


53

W takim przypadku możesz:

template <class T> void function(){
    Baseclass *object = new T();

}

Nie zostanie to skompilowane, jeśli T nie jest podklasą klasy podstawowej (lub T jest klasą podstawową).


a tak, to dobry pomysł. dzięki! Rozumiem, że nie ma sposobu, aby zdefiniować to w definicji szablonu?
phant0m

2
@ phant0m: Dobrze. Nie możesz jawnie ograniczać parametrów szablonu (z wyjątkiem używania pojęć, które były brane pod uwagę dla c ++ 0x, ale następnie zostały odrzucone). Wszystkie ograniczenia powstają niejawnie w wyniku operacji, które na nim wykonujesz (innymi słowy, jedynym ograniczeniem jest „Typ musi obsługiwać wszystkie operacje, które są na nim wykonywane”).
wrzesień

1
ah ic. Wielkie dzięki za wyjaśnienie!
phant0m

8
To wykonuje konstruktora T () i wymaga istnienia konstruktora T (). Zobacz moją odpowiedź na sposób, który pozwala uniknąć tych wymagań.
Douglas Leeder,

3
Ładnie i jasno, ale to jest problem, jeśli T to klasa „ciężka”.
Zapisz

84

Korzystając z kompilatora zgodnego z C ++ 11, możesz zrobić coś takiego:

template<class Derived> class MyClass {

    MyClass() {
        // Compile-time sanity check
        static_assert(std::is_base_of<BaseClass, Derived>::value, "Derived not derived from BaseClass");

        // Do other construction related stuff...
        ...
   }
}

Przetestowałem to przy użyciu kompilatora gcc 4.8.1 w środowisku CYGWIN - więc powinno działać również w środowiskach * nix.


U mnie działa to też tak: template<class TEntity> class BaseBiz { static_assert(std::is_base_of<BaseEntity, TEntity>::value, "TEntity not derived from BaseEntity");...
Matthias Dieter Wallnöfer

1
Myślę, że jest to najbardziej czytelna odpowiedź, która pozwala uniknąć dodatkowego kodu w czasie wykonywania.
Kyle,

50

Aby wykonać mniej bezużyteczny kod w czasie wykonywania, możesz zajrzeć na stronę: http://www.stroustrup.com/bs_faq2.html#constraints, która zawiera kilka klas, które wydajnie wykonują test czasu kompilacji i generują ładniejsze komunikaty o błędach.

W szczególności:

template<class T, class B> struct Derived_from {
        static void constraints(T* p) { B* pb = p; }
        Derived_from() { void(*p)(T*) = constraints; }
};

template<class T> void function() {
    Derived_from<T,Baseclass>();
}

2
Dla mnie to najlepsza i najciekawsza odpowiedź. Koniecznie zajrzyj do FAQ Stroustrupa, aby dowiedzieć się więcej o wszystkich rodzajach ograniczeń, które możesz narzucić w podobny sposób.
Jean-Philippe Pellet

1
Rzeczywiście, to jest piekielna odpowiedź! Dzięki. Wspomniana strona została przeniesiona tutaj: stroustrup.com/bs_faq2.html#constraints
Jan Korous

To świetna odpowiedź. Czy są jakieś dobre sposoby na uniknięcie ostrzeżeń unused variable 'p'i unused variable 'pb'?
Filip S.

@FilipS. dodaj (void)pb;po B* pb = p;.
bit2shift

11

Nie potrzebujesz pojęć, ale możesz użyć SFINAE:

template <typename T>
boost::enable_if< boost::is_base_of<Base,T>::value >::type function() {
   // This function will only be considered by the compiler if
   // T actualy derived from Base
}

Zauważ, że spowoduje to utworzenie instancji funkcji tylko wtedy, gdy warunek zostanie spełniony, ale nie zapewni rozsądnego błędu, jeśli warunek nie zostanie spełniony.


A co jeśli zawiniesz wszystkie funkcje w ten sposób? przy okazji, co to wraca?
the_drow,

enable_ifTrwa drugi typ parametru, który domyślnie void. Wyrażenie enable_if< true, int >::typereprezentuje typ int. Nie bardzo rozumiem, jakie jest twoje pierwsze pytanie, możesz użyć SFINAE do wszystkiego, co chcesz, ale nie do końca rozumiem, co zamierzasz z tym zrobić we wszystkich funkcjach.
David Rodríguez - dribeas

7

Od C ++ 11 nie potrzebujesz Boost ani static_assert. C ++ 11 wprowadza is_base_of i enable_if. C ++ 14 wprowadza wygodny typ enable_if_t, ale jeśli utkniesz z C ++ 11, możesz po prostu użyć enable_if::typezamiast tego.

Alternatywa 1

Rozwiązanie Davida Rodrígueza można przepisać w następujący sposób:

#include <type_traits>

using namespace std;

template <typename T>
enable_if_t<is_base_of<Base, T>::value, void> function() {
   // This function will only be considered by the compiler if
   // T actualy derived from Base
}

Alternatywa 2

Od C ++ 17 mamy is_base_of_v. Rozwiązanie można dalej przepisać na:

#include <type_traits>

using namespace std;

template <typename T>
enable_if_t<is_base_of_v<Base, T>, void> function() {
   // This function will only be considered by the compiler if
   // T actualy derived from Base
}

Alternatywa 3

Możesz także ograniczyć cały szablon. Możesz użyć tej metody do definiowania całych klas. Zwróć uwagę, jak enable_if_tusunięto drugi parametr z (poprzednio był ustawiony na void). Jego domyślna wartość to faktycznie void, ale nie ma to znaczenia, ponieważ jej nie używamy.

#include <type_traits>

using namespace std;

template <typename T,
          typename = enable_if_t<is_base_of_v<Base, T>>>
void function() {
   // This function will only be considered by the compiler if
   // T actualy derived from Base
}

Z dokumentacji parametrów szablonu widzimy, że typename = enable_if_t...jest to parametr szablonu z pustą nazwą. Używamy go po prostu do upewnienia się, że istnieje definicja typu. W szczególności enable_if_tnie zostanie zdefiniowany, jeśli Basenie jest podstawą T.

Powyższa technika jest podana jako przykład w enable_if.


Czy nie byłoby miło, gdyby można było napisać Alternatywę 3 w następujący sposób? template <class T : Base>
Macsinus

4

Można użyć Boost, Concept Sprawdź „s BOOST_CONCEPT_REQUIRES:

#include <boost/concept_check.hpp>
#include <boost/concept/requires.hpp>

template <class T>
BOOST_CONCEPT_REQUIRES(
    ((boost::Convertible<T, BaseClass>)),
(void)) function()
{
    //...
}

0

Wywołując funkcje wewnątrz szablonu, które istnieją w klasie bazowej.

Jeśli spróbujesz utworzyć wystąpienie szablonu z typem, który nie ma dostępu do tej funkcji, zostanie wyświetlony błąd kompilacji.


3
Nie gwarantuje T to, że tak jest,BaseClass ponieważ zadeklarowani członkowie BaseClassmogliby zostać powtórzeni w deklaracji T.
Daniel Trebbien,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.