In Java können Sie generische Klassen definieren, die nur Typen akzeptieren, die die Klasse Ihrer Wahl erweitern, z.
public class ObservableList<T extends List> {
...
}
Dies geschieht mit dem Schlüsselwort „extends“.
Gibt es ein einfaches Äquivalent zu diesem Schlüsselwort in C++?

Raptz
Dies ist in der Regel in C++ nicht gerechtfertigt, wie andere Antworten hier festgestellt haben. In C++ neigen wir dazu, generische Typen basierend auf anderen Einschränkungen als “erbt von dieser Klasse” zu definieren. Wenn Sie das wirklich tun wollten, ist es in C ++ 11 ganz einfach und <type_traits>
:
#include <type_traits>
template<typename T>
class observable_list {
static_assert(std::is_base_of<list, T>::value, "T must inherit from list");
// code here..
};
Dies bricht jedoch viele der Konzepte, die die Leute in C++ erwarten. Es ist besser, Tricks wie das Definieren Ihrer eigenen Eigenschaften zu verwenden. Zum Beispiel vielleicht observable_list
will jeden Containertyp akzeptieren, der die typedefs hat const_iterator
und ein begin
und end
Elementfunktion, die zurückkehrt const_iterator
. Wenn Sie dies auf Klassen beschränken, die erben von list
dann ein Benutzer, der seinen eigenen Typ hat, von dem er nicht erbt list
stellt aber diese Memberfunktionen bereit und Typedefs könnten Ihre nicht verwenden observable_list
.
Es gibt zwei Lösungen für dieses Problem, eine davon besteht darin, nichts einzuschränken und sich auf Duck-Typing zu verlassen. Ein großer Nachteil dieser Lösung ist, dass sie eine enorme Menge an Fehlern enthält, die für Benutzer schwer zu finden sind. Eine andere Lösung besteht darin, Merkmale zu definieren, um den bereitgestellten Typ einzuschränken, um die Schnittstellenanforderungen zu erfüllen. Der große Nachteil dieser Lösung ist, dass zusätzliches Schreiben erforderlich ist, was als lästig empfunden werden kann. Die positive Seite ist jedoch, dass Sie Ihre eigenen Fehlermeldungen a la schreiben können static_assert
.
Der Vollständigkeit halber ist die Lösung für das obige Beispiel angegeben:
#include <type_traits>
template<typename...>
struct void_ {
using type = void;
};
template<typename... Args>
using Void = typename void_<Args...>::type;
template<typename T, typename = void>
struct has_const_iterator : std::false_type {};
template<typename T>
struct has_const_iterator<T, Void<typename T::const_iterator>> : std::true_type {};
struct has_begin_end_impl {
template<typename T, typename Begin = decltype(std::declval<const T&>().begin()),
typename End = decltype(std::declval<const T&>().end())>
static std::true_type test(int);
template<typename...>
static std::false_type test(...);
};
template<typename T>
struct has_begin_end : decltype(has_begin_end_impl::test<T>(0)) {};
template<typename T>
class observable_list {
static_assert(has_const_iterator<T>::value, "Must have a const_iterator typedef");
static_assert(has_begin_end<T>::value, "Must have begin and end member functions");
// code here...
};
Im obigen Beispiel werden viele Konzepte gezeigt, die die Funktionen von C++11 veranschaulichen. Einige Suchbegriffe für Neugierige sind variadische Vorlagen, SFINAE, Ausdrucks-SFINAE und Typeigenschaften.

j_random_hacker
Ich schlage vor, Boost zu verwenden statische Behauptung Funktion in Zusammenarbeit mit is_base_of
aus der Boost Type Traits-Bibliothek:
template<typename T>
class ObservableList {
BOOST_STATIC_ASSERT((is_base_of<List, T>::value)); //Yes, the double parentheses are needed, otherwise the comma will be seen as macro argument separator
...
};
In einigen anderen, einfacheren Fällen können Sie eine globale Vorlage einfach vorwärts deklarieren, sie aber nur für die gültigen Typen definieren (explizit oder teilweise spezialisieren):
template<typename T> class my_template; // Declare, but don't define
// int is a valid type
template<> class my_template<int> {
...
};
// All pointer types are valid
template<typename T> class my_template<T*> {
...
};
// All other types are invalid, and will cause linker error messages.
[Minor EDIT 6/12/2013: Using a declared-but-not-defined template will result in linker, not compiler, error messages.]

jalf
Die einfache Lösung, die noch niemand erwähnt hat, besteht darin, das Problem einfach zu ignorieren. Wenn ich versuche, eine zu verwenden int
als Vorlagentyp in einer Funktionsvorlage, die eine Containerklasse wie Vektor oder Liste erwartet, erhalte ich einen Kompilierungsfehler. Grob und einfach, aber es löst das Problem. Der Compiler versucht, den von Ihnen angegebenen Typ zu verwenden, und wenn dies fehlschlägt, generiert er einen Kompilierungsfehler.
Das einzige Problem dabei ist, dass die Fehlermeldungen, die Sie erhalten, schwierig zu lesen sind. Es ist jedoch eine sehr verbreitete Methode, dies zu tun. Die Standardbibliothek ist voll von Funktions- oder Klassenvorlagen, die ein bestimmtes Verhalten vom Vorlagentyp erwarten und nichts tun, um zu prüfen, ob die verwendeten Typen gültig sind.
Wenn Sie schönere Fehlermeldungen wünschen (oder wenn Sie Fälle abfangen möchten, die keinen Compilerfehler erzeugen würden, aber dennoch keinen Sinn ergeben), können Sie, je nachdem, wie komplex Sie es machen möchten, entweder Boosts statischesasserting oder verwenden die Boost-Bibliothek concept_check.
Mit einem aktuellen Compiler haben Sie eine built_in static_assert
die stattdessen verwendet werden könnte.
Wir können benutzen std::is_base_of
und std::enable_if
:
(static_assert
entfernt werden können, können die oben genannten Klassen benutzerdefiniert implementiert oder verwendet werden Schub wenn wir nicht referenzieren können type_traits
)
#include <type_traits>
#include <list>
class Base {};
class Derived: public Base {};
#if 0 // wrapper
template <class T> class MyClass /* where T:Base */ {
private:
static_assert(std::is_base_of<Base, T>::value, "T is not derived from Base");
typename std::enable_if<std::is_base_of<Base, T>::value, T>::type inner;
};
#elif 0 // base class
template <class T> class MyClass: /* where T:Base */
protected std::enable_if<std::is_base_of<Base, T>::value, T>::type {
private:
static_assert(std::is_base_of<Base, T>::value, "T is not derived from Base");
};
#elif 1 // list-of
template <class T> class MyClass /* where T:list<Base> */ {
static_assert(std::is_base_of<Base, typename T::value_type>::value , "T::value_type is not derived from Base");
typedef typename std::enable_if<std::is_base_of<Base, typename T::value_type>::value, T>::type base;
typedef typename std::enable_if<std::is_base_of<Base, typename T::value_type>::value, T>::type::value_type value_type;
};
#endif
int main() {
#if 0 // wrapper or base-class
MyClass<Derived> derived;
MyClass<Base> base;
// error:
MyClass<int> wrong;
#elif 1 // list-of
MyClass<std::list<Derived>> derived;
MyClass<std::list<Base>> base;
// error:
MyClass<std::list<int>> wrong;
#endif
// all of the static_asserts if not commented out
// or "error: no type named ‘type’ in ‘struct std::enable_if<false, ...>’ pointing to:
// 1. inner
// 2. MyClass
// 3. base + value_type
}

Barry Carr
Soweit ich weiß, ist dies derzeit nicht in C++ möglich. Es gibt jedoch Pläne, dem neuen C++0x-Standard ein Feature namens „Concepts“ hinzuzufügen, das die gewünschte Funktionalität bietet. Dies Wikipedia-Artikel über C++-Konzepte wird es ausführlicher erklären.
Ich weiß, dass dies Ihr unmittelbares Problem nicht behebt, aber es gibt einige C++-Compiler, die bereits damit begonnen haben, Funktionen aus dem neuen Standard hinzuzufügen, sodass es möglich sein könnte, einen Compiler zu finden, der die Konzeptfunktion bereits implementiert hat.

nh_
Ein Äquivalent, das nur vom Typ List abgeleitete Typen T akzeptiert, sieht so aus
template<typename T,
typename std::enable_if<std::is_base_of<List, T>::value>::type* = nullptr>
class ObservableList
{
// ...
};

Gemeinschaft
Ich denke, alle vorherigen Antworten haben den Wald vor lauter Bäumen aus den Augen verloren.
Java-Generika sind nicht dasselbe wie Vorlagen; Sie benutzen Typ löschendie ein dynamische Technikeher, als Kompilierzeit-Polymorphismuswelches ist statische Technik. Es sollte offensichtlich sein, warum diese beiden sehr unterschiedlichen Taktiken nicht gut funktionieren.
Anstatt zu versuchen, ein Kompilierzeitkonstrukt zu verwenden, um ein Laufzeitkonstrukt zu simulieren, schauen wir uns an, was extends
tatsächlich tut: laut Stack Overflow und Wikipediaextend wird verwendet, um Unterklassen anzugeben.
C++ unterstützt auch Unterklassen.
Sie zeigen auch eine Containerklasse, die Typlöschung in Form eines Generikums verwendet und erweitert, um eine Typprüfung durchzuführen. In C++ müssen Sie die Typlöschungsmaschinerie selbst durchführen, was ganz einfach ist: Erstellen Sie einen Zeiger auf die Oberklasse.
Lassen Sie es uns in eine Typedef packen, um die Verwendung zu vereinfachen, anstatt eine ganze Klasse zu erstellen, et voila:
typedef std::list<superclass*> subclasses_of_superclass_only_list;
Zum Beispiel:
class Shape { };
class Triangle : public Shape { };
typedef std::list<Shape*> only_shapes_list;
only_shapes_list shapes;
shapes.push_back(new Triangle()); // Works, triangle is kind of shape
shapes.push_back(new int(30)); // Error, int's are not shapes
Nun scheint List eine Schnittstelle zu sein, die eine Art Sammlung darstellt. Eine Schnittstelle in C++ wäre lediglich eine abstrakte Klasse, also eine Klasse, die nichts als reine virtuelle Methoden implementiert. Mit dieser Methode könnten Sie Ihr Java-Beispiel problemlos in C++ implementieren, ohne Konzepte oder Vorlagenspezialisierungen. Aufgrund der Suche in virtuellen Tabellen würde es auch so langsam wie Generika im Java-Stil arbeiten, aber dies kann oft ein akzeptabler Verlust sein.
9899000cookie-checkC++-Vorlagen, die nur bestimmte Typen akzeptierenyes
schon eine ziemlich alte Frage … Ich glaube, was hier fehlt (auch in den Antworten), ist, dass Java-Generika nicht wirklich ein Äquivalent zu Vorlagen in C ++ sind. Es gibt Ähnlichkeiten, aber meiner Meinung nach sollte man vorsichtig sein, wenn man eine Java-Lösung direkt in C++ übersetzt, nur um zu erkennen, dass sie vielleicht für verschiedene Arten von Problemen gemacht sind;)
– 463035818_ist_keine_Nummer
21. August 2018 um 11:48 Uhr