C++-Vorlagen, die nur bestimmte Typen akzeptieren

Lesezeit: 13 Minuten

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

C Vorlagen die nur bestimmte Typen akzeptieren
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.

    – bvj

    24. Mai 2017 um 5:42 Uhr


  • Wenn sich jemand fragt, was ist template<typename... Args>: en.cppreference.com/w/cpp/language/parameter_pack

    – zardoscht

    23. August 2020 um 21:52 Uhr


1646974213 895 C Vorlagen die nur bestimmte Typen akzeptieren
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.]

  • 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


1646974214 507 C Vorlagen die nur bestimmte Typen akzeptieren
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
}

C Vorlagen die nur bestimmte Typen akzeptieren
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


1646974215 617 C Vorlagen die nur bestimmte Typen akzeptieren
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
{
    // ...
};

  • 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


1646974215 562 C Vorlagen die nur bestimmte Typen akzeptieren
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.

  • 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

989900cookie-checkC++-Vorlagen, die nur bestimmte Typen akzeptieren

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy