Beschränken Sie den C ++ - Vorlagenparameter auf die Unterklasse

80

Wie kann ich erzwingen, dass ein Vorlagenparameter Teine Unterklasse einer bestimmten Klasse ist Baseclass? Etwas wie das:

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

}
phant0m
quelle
3
Was versuchst du damit zu erreichen?
etw
2
Ich möchte nur sicherstellen, dass T tatsächlich eine Instanz einer Unterklasse oder der Klasse selbst ist. Der Code in der von mir bereitgestellten Funktion ist ziemlich irrelevant.
Phant0m
6
im Gegenteil, es ist sehr relevant. Es bestimmt, ob es eine gute Idee ist, Arbeit in diesen Test zu stecken oder nicht. In vielen (allen?) Fällen besteht absolut keine Notwendigkeit, solche Einschränkungen selbst durchzusetzen, sondern der Compiler dies beim Instanziieren tun zu lassen. Für die akzeptierte Antwort wäre es beispielsweise gut, zu überprüfen, ob von Tabgeleitet Baseclass. Ab sofort ist diese Überprüfung implizit und für die Überlastungsauflösung nicht sichtbar. Wenn jedoch nirgends eine solche implizite Einschränkung vorgenommen wird, scheint es keinen Grund für eine künstliche Einschränkung zu geben.
Johannes Schaub - litb
1
Ja, ich stimme zu. Ich wollte jedoch nur wissen, ob es einen Weg gibt, dies zu erreichen oder nicht :) Aber natürlich haben Sie einen sehr gültigen Punkt und bedanken sich für die Einsicht.
Phant0m

Antworten:

51

In diesem Fall können Sie Folgendes tun:

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

}

Dies wird nicht kompilieren , wenn T nicht eine Unterklasse von Basisklasse (oder T ist Basisklasse).

sepp2k
quelle
Ach ja, das ist eine gute Idee. Vielen Dank! Ich nehme an, es gibt keine Möglichkeit, es in der Vorlagendefinition zu definieren.
Phant0m
2
@ phant0m: Richtig. Sie können Vorlagenparameter nicht explizit einschränken (außer bei der Verwendung von Konzepten, die für c ++ 0x berücksichtigt, dann aber gelöscht wurden). Alle Einschränkungen werden implizit durch die Operationen ausgeführt, die Sie daran ausführen (oder mit anderen Worten, die einzige Einschränkung ist "Der Typ muss alle Operationen unterstützen, die daran ausgeführt werden").
sepp2k
1
ah ic. Vielen Dank für die Klarstellung!
Phant0m
8
Dies führt den T () -Konstruktor aus und erfordert die Existenz des T () -Konstruktors. Siehe meine Antwort für einen Weg, der diese Anforderungen vermeidet.
Douglas Leeder
3
Schön und klar, aber das ist ein Problem, wenn T eine "schwere" Klasse ist.
3Dave
84

Mit einem C ++ 11-kompatiblen Compiler können Sie Folgendes tun:

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...
        ...
   }
}

Ich habe dies mit dem Compiler gcc 4.8.1 in einer CYGWIN-Umgebung getestet - daher sollte es auch in * nix-Umgebungen funktionieren.

Vish Desai
quelle
Bei mir funktioniert es auch so: template<class TEntity> class BaseBiz { static_assert(std::is_base_of<BaseEntity, TEntity>::value, "TEntity not derived from BaseEntity");...
Matthias Dieter Wallnöfer
1
Ich denke, dies ist die am besten lesbare Antwort, die zusätzlichen Code zur Laufzeit vermeidet.
Kyle
50

Informationen zur Ausführung von weniger nutzlosem Code zur Laufzeit finden Sie unter: http://www.stroustrup.com/bs_faq2.html#constraints , das einige Klassen bereitstellt, die den Test zur Kompilierungszeit effizient durchführen und schönere Fehlermeldungen erzeugen.

Speziell:

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
quelle
2
Für mich ist dies die beste und interessanteste Antwort. Lesen Sie unbedingt die häufig gestellten Fragen zu Stroustrup, um mehr über alle Arten von Einschränkungen zu erfahren, die Sie ähnlich wie diese durchsetzen könnten.
Jean-Philippe Pellet
1
In der Tat ist dies eine verdammt gute Antwort! Vielen Dank. Die erwähnte Seite wird hierher verschoben: stroustrup.com/bs_faq2.html#constraints
Jan Korous
Dies ist eine großartige Antwort. Gibt es gute Möglichkeiten, um die Warnungen zu vermeiden unused variable 'p'und unused variable 'pb'?
Filip S.
@FilipS. (void)pb;nachher hinzufügen B* pb = p;.
Bit2shift
11

Sie benötigen keine Konzepte, können aber SFINAE verwenden:

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
}

Beachten Sie, dass dies die Funktion nur instanziiert, wenn die Bedingung erfüllt ist, aber keinen vernünftigen Fehler liefert, wenn die Bedingung nicht erfüllt ist.

David Rodríguez - Dribeas
quelle
Was ist, wenn Sie alle Funktionen so umschließen? Übrigens, was gibt es zurück?
the_drow
Der enable_ifnimmt einen zweiten Parameter, der standardmäßig verwendet wird void. Der Ausdruck enable_if< true, int >::typerepräsentiert den Typ int. Ich kann nicht wirklich verstehen, was Ihre erste Frage ist. Sie können SFINAE für alles verwenden, was Sie möchten, aber ich verstehe nicht ganz, was Sie damit über alle Funktionen hinweg vorhaben.
David Rodríguez - Dribeas
7

Seit C ++ 11 benötigen Sie Boost oder nicht mehr static_assert. C ++ 11 führt ein is_base_of und enable_if. In C ++ 14 wird der Convenience-Typ eingeführt enable_if_t. Wenn Sie jedoch mit C ++ 11 nicht weiterkommen, können Sie ihn enable_if::typestattdessen einfach verwenden .

Alternative 1

Die Lösung von David Rodríguez kann wie folgt umgeschrieben werden:

#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
}

Alternative 2

Seit C ++ 17 haben wir is_base_of_v. Die Lösung kann weiter umgeschrieben werden in:

#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
}

Alternative 3

Sie können auch einfach die gesamte Vorlage einschränken. Mit dieser Methode können Sie ganze Klassen definieren. Beachten Sie, wie der zweite Parameter von enable_if_tentfernt wurde (er wurde zuvor auf void gesetzt). Der Standardwert ist tatsächlich void, aber es spielt keine Rolle, da wir ihn nicht verwenden.

#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
}

Aus der Dokumentation der Vorlagenparameter geht hervor, dass typename = enable_if_t...es sich um einen Vorlagenparameter mit einem leeren Namen handelt. Wir verwenden es einfach, um sicherzustellen, dass die Definition eines Typs vorhanden ist. Insbesondere enable_if_twird nicht definiert, wenn Basekeine Basis von ist T.

Die obige Technik ist als Beispiel in angegeben enable_if.

justinpc
quelle
Wäre es nicht schön, wenn es möglich wäre, Alternative 3 wie folgt zu schreiben? template <class T : Base>
Macsinus
4

Sie könnten verwenden Boost - Concept Check ‚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
quelle
0

Durch Aufrufen von Funktionen in Ihrer Vorlage, die in der Basisklasse vorhanden sind.

Wenn Sie versuchen, Ihre Vorlage mit einem Typ zu instanziieren, der keinen Zugriff auf diese Funktion hat, wird ein Fehler beim Kompilieren angezeigt.

DanDan
quelle
3
Dies stellt nicht sicher, dass dies der Fall T ist, BaseClass da die deklarierten Mitglieder BaseClassin der Deklaration von wiederholt werden könnten T.
Daniel Trebbien