Der Grund, warum der Compiler das nicht akzeptiert, ist, dass der Standard es nicht sagt.
Der Grund für die Standard es nicht sagt, ist, dass der Ausschuss nicht tat, was eine Regel einzuführen, dass const MyTemplate<Derived*>
zu const MyTemplate<Base*>
auch eine verwandte Art ist, obwohl die nicht konstante Typen nicht verwandt sind. Und sie wollten sicher keine spezielle Regel für std :: set, da die Sprache im Allgemeinen keine besonderen Fälle für Bibliotheksklassen darstellt.
Der Grund, warum das Standardkomitee diese Typen nicht in Beziehung setzen wollte, ist, dass MyTemplate möglicherweise nicht die Semantik eines Containers hat. Bedenken Sie:
template <typename T>
struct MyTemplate {
T *ptr;
};
template<>
struct MyTemplate<Derived*> {
int a;
void foo();
};
template<>
struct MyTemplate<Base*> {
std::set<double> b;
void bar();
};
Dann was macht es sogar eine const MyTemplate<Derived*>
als ein const MyTemplate<Base*>
passieren bedeuten zu? Die beiden Klassen haben keine gemeinsamen Funktionen und sind nicht Layout-kompatibel. Du brauchst einen Konvertierungsoperator zwischen den beiden, oder der Compiler hat keine Ahnung, was zu tun ist, egal ob sie const sind oder nicht. Aber wie die Templates im Standard definiert sind, der Compiler hat auch ohne die Template-Spezialisierungen keine Ahnung, was zu tun ist.
std::set
könnte selbst einen Konvertierungsoperator bereitstellen, aber das müsste nur eine Kopie (*) machen, die Sie selbst leicht genug machen können. Wenn es so etwas wie ein std::immutable_set
gäbe, dann wäre es möglich, das so zu implementieren, dass ein std::immutable_set<Base*>
aus einem std::immutable_set<Derived*>
konstruiert werden könnte, indem einfach auf denselben pImpl verwiesen wird. Trotzdem würden seltsame Dinge passieren, wenn Sie nicht-virtuelle Operatoren in der abgeleiteten Klasse überladen würden - der Basiscontainer würde die Basisversion aufrufen, also könnte die Konvertierung den Satz de-orderen, wenn er einen nicht standardmäßigen Vergleicher hatte, mit dem irgendetwas gemacht wurde die Objekte selbst statt ihrer Adressen. So würde die Umwandlung mit schweren Vorbehalten kommen. Aber trotzdem gibt es kein immutable_set
, und const ist nicht dasselbe wie unveränderlich.
Angenommen, Derived
ist durch virtuelle oder mehrfache Vererbung mit Base
verknüpft. Dann kann man nicht einfach die Adresse einer Derived
als Adresse einer Base
neuinterpretieren: In den meisten Implementierungen ändert die implizite Umwandlung die Adresse. Daraus folgt, dass Sie eine Struktur, die Derived*
enthält, nicht einfach als eine Struktur mit Base*
im Stapel konvertieren können, ohne die Struktur zu kopieren. Aber der C++ - Standard erlaubt dies tatsächlich für jede Nicht-POD-Klasse, nicht nur mit Mehrfachvererbung. Und Derived
ist nicht POD, da es eine Basisklasse hat. Um diese Änderung zu std::set
zu unterstützen, müssten die Grundlagen von Vererbung und Strukturlayout geändert werden. Es ist eine grundlegende Einschränkung der C++ - Sprache, dass Standardcontainer nicht auf die von Ihnen gewünschte Art und Weise neu interpretiert werden können, und mir sind keine Tricks bekannt, die sie so machen könnten, ohne die Effizienz oder Portabilität oder beides zu reduzieren. Es ist frustrierend, aber dieses Zeug ist schwierig.
Da Ihr Code einen Satz von Wert ist vorbei sowieso, könnte man nur diese Kopie machen:
std::set<Derived*> objs;
register_objects(std::set<Base*>(objs.begin(), objs.end());
[Edit: Sie haben Ihr Codebeispiel geändert nicht von Wert zu übergeben. Mein Code funktioniert immer noch, und afaik ist das Beste, was Sie andere tun können, als die anrufende Code Refactoring ein std::set<Base*>
in erster Linie zu verwenden.]
für std::set<Base*>
einen Wrapper schreiben, die alle Elemente gewährleistet Derived*
, die Art und Weise Java Generics Arbeit , ist einfacher als die Umwandlung zu arrangieren, die Sie effizient sein möchten. So könnte man so etwas wie:
template<typename T, typename U>
struct MySetWrapper {
// Requirement: std::less is consistent. The default probably is,
// but for all we know there are specializations which aren't.
// User beware.
std::set<T> content;
void insert(U value) { content.insert(value); }
// might need a lot more methods, and for the above to return the right
// type, depending how else objs is used.
};
MySetWrapper<Base*,Derived*> objs;
// insert lots of values
register_objects(objs.content);
(*) Eigentlich denke ich, es könnte copy-on-write, was im Fall eines const Parameters in der typischen Art und Weise verwendet werden, bedeuten würde es nie tun muss Kopieren. Aber Copy-on-Write ist bei STL-Implementierungen ein wenig diskreditiert, und selbst wenn es nicht so wäre, würde ich bezweifeln, dass das Komitee solch ein schweres Implementierungsdetail verlangen würde.
Ich korrigierte die Referenzübergabe, das war ein Tippfehler in meiner Frage, in meinem Code habe ich das richtig gemacht. –
Sie sind nicht einfach "einfach anders": es gibt sicherlich eine Beziehung zwischen den Typen, nur ist es nicht bloße Vererbung. – xtofl
aber Template-Substitution funktioniert durch Ersetzen der Template-Parameter in der Klasse, nicht wahr? Die 2 Definitionen können als std :: set_base_ptr und std :: set_derived_ptr betrachtet werden, also grundsätzlich anders. Wenn es getan werden könnte, wäre ich sehr überrascht, würde aber gerne einräumen, dass ich nicht verstanden habe, wie die Template-Substitution funktioniert (was ohne Zweifel dazu beitragen würde, mein Template-Wissen zu erweitern). – Goz