C ++ - Äquivalent zu Java's Instanz von

202

Was ist die bevorzugte Methode, um das C ++ - Äquivalent von Java zu erreichen instanceof?

Yuval Adam
quelle
57
Bevorzugt durch Leistung und Kompatibilität ...
Yuval Adam
7
ist es nicht fair zu fragen "Instanz von - in welcher Sprache?"
Mysticcoder
3
@mysticcoder: Ich bekomme "например на" für Bulgarisch, GT unterstützt C ++ allerdings nicht
Mark K Cowan

Antworten:

200

Versuchen Sie es mit:

if(NewType* v = dynamic_cast<NewType*>(old)) {
   // old was safely casted to NewType
   v->doSomething();
}

Dies setzt voraus, dass Ihr Compiler die Rtti-Unterstützung aktiviert hat.

EDIT: Ich habe einige gute Kommentare zu dieser Antwort gehabt!

Jedes Mal, wenn Sie einen dynamic_cast (oder eine Instanz davon) verwenden müssen, sollten Sie sich fragen, ob dies erforderlich ist. Es ist im Allgemeinen ein Zeichen für schlechtes Design.

Typische Problemumgehungen bestehen darin, das spezielle Verhalten für die Klasse, nach der Sie suchen, in eine virtuelle Funktion in der Basisklasse zu integrieren oder möglicherweise etwas wie einen Besucher einzuführen, in dem Sie ein bestimmtes Verhalten für Unterklassen einführen können, ohne die Schnittstelle zu ändern (außer das Hinzufügen der Besucherakzeptanzschnittstelle von Kurs).

Wie bereits erwähnt, ist dynamic_cast nicht kostenlos. Ein einfacher und konsistent ausführender Hack, der die meisten (aber nicht alle Fälle) behandelt, besteht im Grunde darin, eine Aufzählung hinzuzufügen, die alle möglichen Typen Ihrer Klasse darstellt, und zu prüfen, ob Sie den richtigen haben.

if(old->getType() == BOX) {
   Box* box = static_cast<Box*>(old);
   // Do something box specific
}

Dies ist kein gutes Design, aber es kann eine Problemumgehung sein und seine Kosten sind mehr oder weniger nur ein virtueller Funktionsaufruf. Es funktioniert auch unabhängig davon, ob RTTI aktiviert ist oder nicht.

Beachten Sie, dass dieser Ansatz nicht mehrere Vererbungsebenen unterstützt. Wenn Sie also nicht aufpassen, können Sie mit Code enden, der wie folgt aussieht:

// Here we have a SpecialBox class that inherits Box, since it has its own type
// we must check for both BOX or SPECIAL_BOX
if(old->getType() == BOX || old->getType() == SPECIAL_BOX) {
   Box* box = static_cast<Box*>(old);
   // Do something box specific
}
Laserallan
quelle
4
Dies ist
7
Wenn Sie instanceof verwenden müssen, stimmt in den meisten Fällen etwas mit Ihrem Design nicht.
Mslot
24
Vergessen Sie nicht, dass dynamic_cast eine Operation mit hohen Kosten ist.
Klaim
13
Es gibt viele Beispiele für sinnvolle Anwendungen der dynamischen Typprüfung. Es ist normalerweise nicht bevorzugt, aber es hat einen Platz. (Warum sollte es oder sein Äquivalent sonst in jeder wichtigen OO-Sprache erscheinen: C ++, Java, Python usw.?)
Paul Draper
2
Ich würde beide auf IOException-Ebene abfangen, wenn sie nicht unterschiedlich behandelt werden müssen. Wenn sie unterschiedlich behandelt werden müssen, würde ich für jede Ausnahme einen catch-Block hinzufügen.
Mslot
37

Je nachdem, was Sie tun möchten, können Sie Folgendes tun:

template<typename Base, typename T>
inline bool instanceof(const T*) {
    return std::is_base_of<Base, T>::value;
}

Verwenden:

if (instanceof<BaseClass>(ptr)) { ... }

Dies funktioniert jedoch nur bei den dem Compiler bekannten Typen.

Bearbeiten:

Dieser Code sollte für polymorphe Zeiger funktionieren:

template<typename Base, typename T>
inline bool instanceof(const T *ptr) {
    return dynamic_cast<const Base*>(ptr) != nullptr;
}

Beispiel: http://cpp.sh/6qir

Panzi
quelle
Elegante und gut gemachte Lösung. +1 Achten Sie jedoch darauf, den richtigen Zeiger zu erhalten. Nicht gültig für polymorphen Zeiger?
Adrian Maire
Was ist, wenn wir den Zeiger bei Verwendung dieser Funktion zurückgestellt haben? Würde es dann für polymorphe Zeiger funktionieren?
mark.kedzierski
Nein, dies funktioniert nur bei den Typen, die dem Compiler bekannt sind. Funktioniert nicht mit polymorphen Zeigern, egal ob Sie derefernece sind oder nicht. Ich werde etwas hinzufügen, das in diesem Fall funktionieren könnte.
Panzi
2
Ich habe Ihr Beispiel geändert, um eine Version dieser Methode zu schreiben, die Referenzen anstelle von Zeigern verwendet: cpp.sh/8owv
Sri Harsha Chilakapati
Warum hat der Zieltyp der dynamischen Besetzung "const"?
user1056903
7

Implementierungsinstanz ohne dynamic_cast

Ich denke, diese Frage ist heute noch relevant. Mit dem C ++ 11-Standard können Sie jetzt eine instanceofFunktion implementieren , ohne Folgendes zu verwenden dynamic_cast:

if (dynamic_cast<B*>(aPtr) != nullptr) {
  // aPtr is instance of B
} else {
  // aPtr is NOT instance of B
}

Aber Sie sind immer noch auf RTTIUnterstützung angewiesen . Hier ist meine Lösung für dieses Problem, abhängig von einigen Makros und Metaprogrammiermagie. Der einzige Nachteil imho ist, dass dieser Ansatz nicht für Mehrfachvererbung funktioniert .

InstanceOfMacros.h

#include <set>
#include <tuple>
#include <typeindex>

#define _EMPTY_BASE_TYPE_DECL() using BaseTypes = std::tuple<>;
#define _BASE_TYPE_DECL(Class, BaseClass) \
  using BaseTypes = decltype(std::tuple_cat(std::tuple<BaseClass>(), Class::BaseTypes()));
#define _INSTANCE_OF_DECL_BODY(Class)                                 \
  static const std::set<std::type_index> baseTypeContainer;           \
  virtual bool instanceOfHelper(const std::type_index &_tidx) {       \
    if (std::type_index(typeid(ThisType)) == _tidx) return true;      \
    if (std::tuple_size<BaseTypes>::value == 0) return false;         \
    return baseTypeContainer.find(_tidx) != baseTypeContainer.end();  \
  }                                                                   \
  template <typename... T>                                            \
  static std::set<std::type_index> getTypeIndexes(std::tuple<T...>) { \
    return std::set<std::type_index>{std::type_index(typeid(T))...};  \
  }

#define INSTANCE_OF_SUB_DECL(Class, BaseClass) \
 protected:                                    \
  using ThisType = Class;                      \
  _BASE_TYPE_DECL(Class, BaseClass)            \
  _INSTANCE_OF_DECL_BODY(Class)

#define INSTANCE_OF_BASE_DECL(Class)                                                    \
 protected:                                                                             \
  using ThisType = Class;                                                               \
  _EMPTY_BASE_TYPE_DECL()                                                               \
  _INSTANCE_OF_DECL_BODY(Class)                                                         \
 public:                                                                                \
  template <typename Of>                                                                \
  typename std::enable_if<std::is_base_of<Class, Of>::value, bool>::type instanceOf() { \
    return instanceOfHelper(std::type_index(typeid(Of)));                               \
  }

#define INSTANCE_OF_IMPL(Class) \
  const std::set<std::type_index> Class::baseTypeContainer = Class::getTypeIndexes(Class::BaseTypes());

Demo

Sie können dieses Zeug dann ( mit Vorsicht ) wie folgt verwenden:

DemoClassHierarchy.hpp *

#include "InstanceOfMacros.h"

struct A {
  virtual ~A() {}
  INSTANCE_OF_BASE_DECL(A)
};
INSTANCE_OF_IMPL(A)

struct B : public A {
  virtual ~B() {}
  INSTANCE_OF_SUB_DECL(B, A)
};
INSTANCE_OF_IMPL(B)

struct C : public A {
  virtual ~C() {}
  INSTANCE_OF_SUB_DECL(C, A)
};
INSTANCE_OF_IMPL(C)

struct D : public C {
  virtual ~D() {}
  INSTANCE_OF_SUB_DECL(D, C)
};
INSTANCE_OF_IMPL(D)

Der folgende Code enthält eine kleine Demo, um das korrekte Verhalten rudimentär zu überprüfen.

InstanceOfDemo.cpp

#include <iostream>
#include <memory>
#include "DemoClassHierarchy.hpp"

int main() {
  A *a2aPtr = new A;
  A *a2bPtr = new B;
  std::shared_ptr<A> a2cPtr(new C);
  C *c2dPtr = new D;
  std::unique_ptr<A> a2dPtr(new D);

  std::cout << "a2aPtr->instanceOf<A>(): expected=1, value=" << a2aPtr->instanceOf<A>() << std::endl;
  std::cout << "a2aPtr->instanceOf<B>(): expected=0, value=" << a2aPtr->instanceOf<B>() << std::endl;
  std::cout << "a2aPtr->instanceOf<C>(): expected=0, value=" << a2aPtr->instanceOf<C>() << std::endl;
  std::cout << "a2aPtr->instanceOf<D>(): expected=0, value=" << a2aPtr->instanceOf<D>() << std::endl;
  std::cout << std::endl;
  std::cout << "a2bPtr->instanceOf<A>(): expected=1, value=" << a2bPtr->instanceOf<A>() << std::endl;
  std::cout << "a2bPtr->instanceOf<B>(): expected=1, value=" << a2bPtr->instanceOf<B>() << std::endl;
  std::cout << "a2bPtr->instanceOf<C>(): expected=0, value=" << a2bPtr->instanceOf<C>() << std::endl;
  std::cout << "a2bPtr->instanceOf<D>(): expected=0, value=" << a2bPtr->instanceOf<D>() << std::endl;
  std::cout << std::endl;
  std::cout << "a2cPtr->instanceOf<A>(): expected=1, value=" << a2cPtr->instanceOf<A>() << std::endl;
  std::cout << "a2cPtr->instanceOf<B>(): expected=0, value=" << a2cPtr->instanceOf<B>() << std::endl;
  std::cout << "a2cPtr->instanceOf<C>(): expected=1, value=" << a2cPtr->instanceOf<C>() << std::endl;
  std::cout << "a2cPtr->instanceOf<D>(): expected=0, value=" << a2cPtr->instanceOf<D>() << std::endl;
  std::cout << std::endl;
  std::cout << "c2dPtr->instanceOf<A>(): expected=1, value=" << c2dPtr->instanceOf<A>() << std::endl;
  std::cout << "c2dPtr->instanceOf<B>(): expected=0, value=" << c2dPtr->instanceOf<B>() << std::endl;
  std::cout << "c2dPtr->instanceOf<C>(): expected=1, value=" << c2dPtr->instanceOf<C>() << std::endl;
  std::cout << "c2dPtr->instanceOf<D>(): expected=1, value=" << c2dPtr->instanceOf<D>() << std::endl;
  std::cout << std::endl;
  std::cout << "a2dPtr->instanceOf<A>(): expected=1, value=" << a2dPtr->instanceOf<A>() << std::endl;
  std::cout << "a2dPtr->instanceOf<B>(): expected=0, value=" << a2dPtr->instanceOf<B>() << std::endl;
  std::cout << "a2dPtr->instanceOf<C>(): expected=1, value=" << a2dPtr->instanceOf<C>() << std::endl;
  std::cout << "a2dPtr->instanceOf<D>(): expected=1, value=" << a2dPtr->instanceOf<D>() << std::endl;

  delete a2aPtr;
  delete a2bPtr;
  delete c2dPtr;

  return 0;
}

Ausgabe:

a2aPtr->instanceOf<A>(): expected=1, value=1
a2aPtr->instanceOf<B>(): expected=0, value=0
a2aPtr->instanceOf<C>(): expected=0, value=0
a2aPtr->instanceOf<D>(): expected=0, value=0

a2bPtr->instanceOf<A>(): expected=1, value=1
a2bPtr->instanceOf<B>(): expected=1, value=1
a2bPtr->instanceOf<C>(): expected=0, value=0
a2bPtr->instanceOf<D>(): expected=0, value=0

a2cPtr->instanceOf<A>(): expected=1, value=1
a2cPtr->instanceOf<B>(): expected=0, value=0
a2cPtr->instanceOf<C>(): expected=1, value=1
a2cPtr->instanceOf<D>(): expected=0, value=0

c2dPtr->instanceOf<A>(): expected=1, value=1
c2dPtr->instanceOf<B>(): expected=0, value=0
c2dPtr->instanceOf<C>(): expected=1, value=1
c2dPtr->instanceOf<D>(): expected=1, value=1

a2dPtr->instanceOf<A>(): expected=1, value=1
a2dPtr->instanceOf<B>(): expected=0, value=0
a2dPtr->instanceOf<C>(): expected=1, value=1
a2dPtr->instanceOf<D>(): expected=1, value=1

Performance

Die interessanteste Frage, die sich jetzt stellt, ist, ob dieses böse Zeug effizienter ist als die Verwendung von dynamic_cast. Deshalb habe ich eine sehr einfache App zur Leistungsmessung geschrieben.

InstanceOfPerformance.cpp

#include <chrono>
#include <iostream>
#include <string>
#include "DemoClassHierarchy.hpp"

template <typename Base, typename Derived, typename Duration>
Duration instanceOfMeasurement(unsigned _loopCycles) {
  auto start = std::chrono::high_resolution_clock::now();
  volatile bool isInstanceOf = false;
  for (unsigned i = 0; i < _loopCycles; ++i) {
    Base *ptr = new Derived;
    isInstanceOf = ptr->template instanceOf<Derived>();
    delete ptr;
  }
  auto end = std::chrono::high_resolution_clock::now();
  return std::chrono::duration_cast<Duration>(end - start);
}

template <typename Base, typename Derived, typename Duration>
Duration dynamicCastMeasurement(unsigned _loopCycles) {
  auto start = std::chrono::high_resolution_clock::now();
  volatile bool isInstanceOf = false;
  for (unsigned i = 0; i < _loopCycles; ++i) {
    Base *ptr = new Derived;
    isInstanceOf = dynamic_cast<Derived *>(ptr) != nullptr;
    delete ptr;
  }
  auto end = std::chrono::high_resolution_clock::now();
  return std::chrono::duration_cast<Duration>(end - start);
}

int main() {
  unsigned testCycles = 10000000;
  std::string unit = " us";
  using DType = std::chrono::microseconds;

  std::cout << "InstanceOf performance(A->D)  : " << instanceOfMeasurement<A, D, DType>(testCycles).count() << unit
            << std::endl;
  std::cout << "InstanceOf performance(A->C)  : " << instanceOfMeasurement<A, C, DType>(testCycles).count() << unit
            << std::endl;
  std::cout << "InstanceOf performance(A->B)  : " << instanceOfMeasurement<A, B, DType>(testCycles).count() << unit
            << std::endl;
  std::cout << "InstanceOf performance(A->A)  : " << instanceOfMeasurement<A, A, DType>(testCycles).count() << unit
            << "\n"
            << std::endl;
  std::cout << "DynamicCast performance(A->D) : " << dynamicCastMeasurement<A, D, DType>(testCycles).count() << unit
            << std::endl;
  std::cout << "DynamicCast performance(A->C) : " << dynamicCastMeasurement<A, C, DType>(testCycles).count() << unit
            << std::endl;
  std::cout << "DynamicCast performance(A->B) : " << dynamicCastMeasurement<A, B, DType>(testCycles).count() << unit
            << std::endl;
  std::cout << "DynamicCast performance(A->A) : " << dynamicCastMeasurement<A, A, DType>(testCycles).count() << unit
            << "\n"
            << std::endl;
  return 0;
}

Die Ergebnisse variieren und basieren im Wesentlichen auf dem Grad der Compileroptimierung. Das Kompilieren des Leistungsmessprogramms unter Verwendung g++ -std=c++11 -O0 -o instanceof-performance InstanceOfPerformance.cppder Ausgabe auf meinem lokalen Computer war:

InstanceOf performance(A->D)  : 699638 us
InstanceOf performance(A->C)  : 642157 us
InstanceOf performance(A->B)  : 671399 us
InstanceOf performance(A->A)  : 626193 us

DynamicCast performance(A->D) : 754937 us
DynamicCast performance(A->C) : 706766 us
DynamicCast performance(A->B) : 751353 us
DynamicCast performance(A->A) : 676853 us

Mhm, dieses Ergebnis war sehr ernüchternd, da das Timing zeigt, dass der neue Ansatz im Vergleich zum dynamic_castAnsatz nicht viel schneller ist . Es ist noch weniger effizient für den speziellen Testfall, der prüft, ob ein Zeiger von Aeine Instanz von ist A. ABER das Blatt wendet sich, indem wir unsere Binärdatei mithilfe der Compiler-Otpimierung optimieren. Der jeweilige Compilerbefehl lautet g++ -std=c++11 -O3 -o instanceof-performance InstanceOfPerformance.cpp. Das Ergebnis auf meinem lokalen Computer war erstaunlich:

InstanceOf performance(A->D)  : 3035 us
InstanceOf performance(A->C)  : 5030 us
InstanceOf performance(A->B)  : 5250 us
InstanceOf performance(A->A)  : 3021 us

DynamicCast performance(A->D) : 666903 us
DynamicCast performance(A->C) : 698567 us
DynamicCast performance(A->B) : 727368 us
DynamicCast performance(A->A) : 3098 us

Wenn Sie nicht auf Mehrfachvererbung angewiesen sind, kein Gegner guter alter C-Makros, RTTI- und Vorlagen-Metaprogrammierung sind und nicht zu faul sind, um den Klassen Ihrer Klassenhierarchie einige kleine Anweisungen hinzuzufügen, kann dieser Ansatz Ihre Anwendung ein wenig verbessern in Bezug auf seine Leistung, wenn Sie häufig die Instanz eines Zeigers überprüfen. Aber verwenden Sie es mit Vorsicht . Es gibt keine Garantie für die Richtigkeit dieses Ansatzes.

Hinweis: Alle Demos wurden clang (Apple LLVM version 9.0.0 (clang-900.0.39.2))unter macOS Sierra auf einem MacBook Pro Mid 2012 kompiliert .

Bearbeiten: Ich habe auch die Leistung auf einem Linux-Computer mit getestet gcc (Ubuntu 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609. Auf dieser Plattform war der Leistungsvorteil nicht so signifikant wie auf MacOs mit Clang.

Ausgabe (ohne Compileroptimierung):

InstanceOf performance(A->D)  : 390768 us
InstanceOf performance(A->C)  : 333994 us
InstanceOf performance(A->B)  : 334596 us
InstanceOf performance(A->A)  : 300959 us

DynamicCast performance(A->D) : 331942 us
DynamicCast performance(A->C) : 303715 us
DynamicCast performance(A->B) : 400262 us
DynamicCast performance(A->A) : 324942 us

Ausgabe (mit Compileroptimierung):

InstanceOf performance(A->D)  : 209501 us
InstanceOf performance(A->C)  : 208727 us
InstanceOf performance(A->B)  : 207815 us
InstanceOf performance(A->A)  : 197953 us

DynamicCast performance(A->D) : 259417 us
DynamicCast performance(A->C) : 256203 us
DynamicCast performance(A->B) : 261202 us
DynamicCast performance(A->A) : 193535 us
andi1337
quelle
Gut durchdachte Antwort! Ich bin froh, dass Sie die Zeitangaben gemacht haben. Dies war eine interessante Lektüre.
Eric
0

dynamic_castist als ineffizient bekannt. Es durchläuft die Vererbungshierarchie und ist die einzige Lösung, wenn Sie über mehrere Vererbungsebenen verfügen und prüfen müssen, ob ein Objekt eine Instanz eines der Typen in seiner Typhierarchie ist.

Wenn jedoch eine eingeschränktere Form instanceofdavon nur prüft, ob ein Objekt genau dem von Ihnen angegebenen Typ entspricht, der für Ihre Anforderungen ausreicht, ist die folgende Funktion viel effizienter:

template<typename T, typename K>
inline bool isType(const K &k) {
    return typeid(T).hash_code() == typeid(k).hash_code();
}

Hier ist ein Beispiel, wie Sie die obige Funktion aufrufen würden:

DerivedA k;
Base *p = &k;

cout << boolalpha << isType<DerivedA>(*p) << endl;  // true
cout << boolalpha << isType<DerivedB>(*p) << endl;  // false

Sie geben den Vorlagentyp an A(als den Typ, nach dem Sie suchen) und übergeben das zu testende Objekt als Argument (aus dem der Vorlagentyp Kabgeleitet wird).

Arjun Menon
quelle
Der Standard verlangt nicht, dass hash_code für verschiedene Typen eindeutig ist, daher ist dies unzuverlässig.
Mattnz
2
Ist typeid (T) selbst nicht mit Gleichheit vergleichbar, sodass kein Vertrauen in den Hashcode erforderlich ist?
Paul Stelian
-5
#include <iostream.h>
#include<typeinfo.h>

template<class T>
void fun(T a)
{
  if(typeid(T) == typeid(int))
  {
     //Do something
     cout<<"int";
  }
  else if(typeid(T) == typeid(float))
  {
     //Do Something else
     cout<<"float";
  }
}

void main()
 {
      fun(23);
      fun(90.67f);
 }
HHH
quelle
1
Dies ist ein wirklich schlechtes Beispiel. Warum nicht Überladung verwenden, das ist billiger?
user1095108
11
Das Hauptproblem ist, dass die Frage nicht beantwortet werden kann. instanceoffragt den dynamischen Typ ab, aber in dieser Antwort stimmen der dynamische und der statische Typ immer überein.
MSalters
@HHH Sie antworten ist weit weg von der gestellten Frage!
Programmierer
-11

Dies funktionierte perfekt für mich mit Code :: Blocks IDE mit GCC-Konformität

#include<iostream>
#include<typeinfo>
#include<iomanip>
#define SIZE 20
using namespace std;

class Publication
{
protected:
    char title[SIZE];
    int price;

public:
    Publication()
    {
        cout<<endl<<" Enter title of media : ";
        cin>>title;

        cout<<endl<<" Enter price of media : ";
        cin>>price;
    }

    virtual void show()=0;
};

class Book : public Publication
{
    int pages;

public:
    Book()
    {
        cout<<endl<<" Enter number of pages : ";
        cin>>pages;
    }

    void show()
    {
        cout<<endl<<setw(12)<<left<<" Book Title"<<": "<<title;
        cout<<endl<<setw(12)<<left<<" Price"<<": "<<price;
        cout<<endl<<setw(12)<<left<<" Pages"<<": "<<pages;
        cout<<endl<<" ----------------------------------------";
    }
};

class Tape : public Publication
{
    int duration;

public:
    Tape()
    {
        cout<<endl<<" Enter duration in minute : ";
        cin>>duration;
    }

    void show()
    {
        cout<<endl<<setw(10)<<left<<" Tape Title"<<": "<<title;
        cout<<endl<<setw(10)<<left<<" Price"<<": "<<price;
        cout<<endl<<setw(10)<<left<<" Duration"<<": "<<duration<<" minutes";
        cout<<endl<<" ----------------------------------------";
    }
};
int main()
{
    int n, i, type;

    cout<<endl<<" Enter number of media : ";
    cin>>n;

    Publication **p = new Publication*[n];
    cout<<endl<<" Enter "<<n<<" media details : ";

    for(i=0;i<n;i++)
    {
        cout<<endl<<" Select Media Type [ 1 - Book / 2 - Tape ] ";
        cin>>type;

        if ( type == 1 )
        {
            p[i] = new Book();
        }
        else
        if ( type == 2 )
        {
            p[i] = new Tape();
        }
        else
        {
            i--;
            cout<<endl<<" Invalid type. You have to Re-enter choice";
        }
    }

    for(i=0;i<n;i++)
    {
        if ( typeid(Book) == typeid(*p[i]) )
        {
            p[i]->show();
        }
    }

    return 0;
}
pgp
quelle
1
@programmer Ich denke du willst @pgp aufrufen, ich habe einfach seine Code-Formatierung korrigiert. Außerdem scheint seine Antwort im Grunde genommen "use typeid" zu sein, was zwar falsch ist ("Es gibt keine Garantie dafür, dass bei allen Auswertungen des typeid-Ausdrucks auf demselben Typ auf dieselbe std :: type_info-Instanz verwiesen wird ... assert(typeid(A) == typeid(A)); /* not guaranteed */", siehe cppreference.com ) gibt an, dass er zumindest versucht hat, die Frage zu beantworten, wenn auch nicht hilfreich, weil er es versäumt hat, ein minimales Arbeitsbeispiel anzubieten.
Andres Riofrio