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();

}
phant0m
źródło
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ą).

sepp2k
źródło
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.

Vish Desai
źródło
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>();
}
Douglas Leeder
źródło
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.

David Rodríguez - dribeas
źródło
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.

justinpc
źródło
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()
{
    //...
}
Daniel Trebbien
źródło
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.

DanDan
źródło
3
Nie gwarantuje T to, że tak jest,BaseClass ponieważ zadeklarowani członkowie BaseClassmogliby zostać powtórzeni w deklaracji T.
Daniel Trebbien,