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++?
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
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.
Ich habe bis heute nie bemerkt, dass C++-Templates Ententypisierung verwenden. Irgendwie bizarr!
– Andy
9. Juni 2015 um 13:36 Uhr
Angesichts der umfangreichen politischen Einschränkungen C++ vorgestellt Cnicht sicher warum template<class T:list> ist so ein beleidigendes Konzept. Danke für den Tipp.
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.]
Statische Behauptungen sind auch nett. 🙂
– Makrele
17. Mai 2009 um 10:46 Uhr
@John: Ich fürchte, dass die Spezialisierung nur übereinstimmen würde myBaseType exakt. Bevor Sie Boost verwerfen, sollten Sie wissen, dass es sich größtenteils um Template-Code handelt, der nur aus Headern besteht. Es gibt also zur Laufzeit keinen Speicher- oder Zeitaufwand für Dinge, die Sie nicht verwenden. Auch die besonderen Dinge, die Sie hier verwenden würden (BOOST_STATIC_ASSERT() und is_base_of<>) kann nur mit implementiert werden Erklärungen (also keine tatsächliche Definitionen von Funktionen oder Variablen), sodass sie weder Platz noch Zeit beanspruchen.
– j_random_hacker
11. Dezember 2011 um 23:20 Uhr
C++11 ist da. Jetzt können wir verwenden static_assert(std::is_base_of<List, T>::value, "T must extend list").
– Siyuan Ren
10. September 2013 um 2:55 Uhr
Übrigens ist die doppelte Klammer notwendig, weil BOOST_STATIC_ASSERT ein Makro ist und die zusätzliche Klammer den Präprozessor daran hindert, das Komma in den is_base_of-Funktionsargumenten als zweites Makroargument zu interpretieren.
– jfritz42
13. August 2015 um 19:29 Uhr
@Andreyua: Ich verstehe nicht wirklich, was fehlt. Du könntest versuchen, eine Variable zu deklarieren my_template<int> x; oder my_template<float**> y; und vergewissern Sie sich, dass der Compiler dies zulässt, und deklarieren Sie dann eine Variable my_template<char> z; und vergewissern Sie sich, dass dies nicht der Fall ist.
– j_random_hacker
16. November 2017 um 16:20 Uhr
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_assertdie stattdessen verwendet werden könnte.
Ja, ich dachte immer, dass Templates dem Ententippen in C++ am nächsten kommen. Wenn es alle für eine Vorlage erforderlichen Elemente enthält, kann es in einer Vorlage verwendet werden.
– Benutzer3458
18. Mai 2009 um 0:12 Uhr
@John: Tut mir leid, ich kann mir daraus keinen Kopf oder Zahl machen. Welche Art ist T, und woher wird dieser Code aufgerufen? Ohne Kontext habe ich keine Chance, dieses Code-Snippet zu verstehen. Aber was ich gesagt habe, ist wahr. Wenn Sie versuchen anzurufen toString() auf einem Typ, der kein a hat toString Member-Funktion, dann erhalten Sie einen Kompilierungsfehler.
– jalf
11. Dezember 2011 um 21:44 Uhr
@John: Beim nächsten Mal sollten Sie vielleicht etwas weniger triggerfreudig sein, wenn Sie Leute ablehnen, wenn das Problem in Ihrem Code liegt
– jalf
12. Dezember 2011 um 8:12 Uhr
@ jalf, ok. +1. Dies war eine großartige Antwort, die nur versucht hat, das Beste daraus zu machen. Tut mir leid, dass ich mich verlesen habe. Ich dachte, wir sprachen über die Verwendung des Typs als Parameter für Klassen, nicht für Funktionsvorlagen, die meiner Meinung nach Mitglieder der ersteren sind, aber aufgerufen werden müssen, damit der Compiler sie kennzeichnet.
– John
12. Dezember 2011 um 10:52 Uhr
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.
Konzepte wurden leider aus dem Standard gestrichen.
– Makrele
24. Juli 2009 um 8:38 Uhr
Einschränkungen und Konzepte sollten für C++20 übernommen werden.
– Petr Jaworik
28. Juni 2018 um 17:50 Uhr
Es ist auch ohne Konzepte möglich, mit static_assert und SFINAE, wie die anderen Antworten zeigen. Das verbleibende Problem für jemanden, der von Java oder C# oder Haskell(…) kommt, ist, dass der C++20-Compiler keine Definitionsprüfung anhand der erforderlichen Konzepte durchführt, was Java und C# tun.
– Benutzer7610
15. Dezember 2019 um 19:55 Uhr
nh_
Ein Äquivalent, das nur vom Typ List abgeleitete Typen T akzeptiert, sieht so aus
Konzepte wurden leider aus dem Standard gestrichen.
– Makrele
24. Juli 2009 um 8:38 Uhr
Einschränkungen und Konzepte sollten für C++20 übernommen werden.
– Petr Jaworik
28. Juni 2018 um 17:50 Uhr
Es ist auch ohne Konzepte möglich, mit static_assert und SFINAE, wie die anderen Antworten zeigen. Das verbleibende Problem für jemanden, der von Java oder C# oder Haskell(…) kommt, ist, dass der C++20-Compiler keine Definitionsprüfung anhand der erforderlichen Konzepte durchführt, was Java und C# tun.
– Benutzer7610
15. Dezember 2019 um 19:55 Uhr
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:
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.
Ich bin kein Fan von Antworten, die Sätze wie „es sollte offensichtlich sein“ oder „jeder weiß“ verwenden und dann erklären, was offensichtlich oder allgemein bekannt ist. Offensichtlich ist relativ zu Kontext, Erfahrung und Erfahrungskontext. Solche Aussagen sind von Natur aus unhöflich.
– 3Dave
8. Juli 2016 um 23:31 Uhr
@DavidLively Es ist ungefähr zwei Jahre zu spät, um diese Antwort wegen der Etikette zu kritisieren, aber ich stimme Ihnen in diesem speziellen Fall auch nicht zu. Ich habe erklärt, warum diese beiden Techniken nicht zusammenpassen Vor Er sagte, es sei offensichtlich, nicht danach. Ich lieferte den Kontext und sagte dann, dass die Schlussfolgerung aus diesem Kontext offensichtlich sei. Das passt nicht ganz in dein Schema.
– Alice
10. Juli 2016 um 5:30 Uhr
Der Autor dieser Antwort sagte, dass etwas nach schwerem Heben offensichtlich sei. Ich glaube nicht, dass der Autor sagen wollte, dass die Lösung offensichtlich war.
– Lukas Gehorsam
13. November 2019 um 0:53 Uhr
Es ist überhaupt nicht offensichtlich, warum die beiden Techniken nicht gut zusammenspielen, oder sogar, dass sie es müssen, da dpm als Template-Parameter-Einschränkungen mit beiden identisch sein müssen.
– Robin Davis
20. September 2021 um 6:47 Uhr
Es ist überhaupt nicht offensichtlich, warum die beiden Techniken nicht gut zusammenspielen, oder sogar, dass sie das tun müssen, da Template-Parameter-Einschränkungen nicht gleich sein müssen. Sogar Strousstrup war erstaunt, dass das Problem in C++0x nicht angegangen wurde, da es ganz oben auf seiner Prioritätenliste stand. Der Schablonen-Metaprogrammierungs-Bodge, der an seiner Stelle bereitgestellt wurde, ist unentschuldbar. Eine prägnante Art zu spezifizieren, dass “übereinstimmende Klassen diese Methoden implementieren müssen (virtuell oder nicht virtuell)”, hätte 99 % der Anforderungen für Nicht-STL-Programmierer erfüllt. (35+ Jahre C++ Veteran)
– Robin Davis
20. September 2021 um 7:02 Uhr
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