Ist es möglich, std::optional
für benutzerdefinierte Typen zu spezialisieren? Wenn nicht, ist es zu spät, dies dem Standard vorzuschlagen?
Mein Anwendungsfall dafür ist eine ganzzahlige Klasse, die einen Wert innerhalb eines Bereichs darstellt. Zum Beispiel könnten Sie eine Ganzzahl haben, die irgendwo im Bereich [0, 10] liegt. Viele meiner Anwendungen sind selbst für ein einziges Byte Overhead empfindlich, so dass ich aufgrund der zusätzlichen std::optional
keine nicht spezialisierte bool
verwenden könnte. Eine Spezialisierung für std::optional
wäre jedoch für eine ganze Zahl, deren Bereich kleiner als der zugrunde liegende Typ ist, trivial. Wir könnten einfach den Wert 11
in meinem Beispiel speichern. Dies sollte keinen Platz- oder Zeitaufwand gegenüber einem nicht optionalen Wert bieten.
Darf ich diese Spezialisierung in namespace std
erstellen?
Es gilt die allgemeine Regel in 17.6.4.2.1 [namespace.std] / 1:
Ein Programm kann nur dann eine Vorlagenspezialisierung für eine Standardbibliotheksvorlage zum Namespace
std
hinzufügen, wenn die Deklaration von einem benutzerdefinierten Typ abhängt und die Spezialisierung die Standardbibliotheksanforderungen für die ursprüngliche Vorlage erfüllt und nicht explizit ist verboten.
Also würde ich sagen, es ist erlaubt.
N.B. optional
ist nicht Bestandteil des C ++ 14-Standards, sondern wird in einer separaten technischen Spezifikation zu den Grundlagen der Bibliothek enthalten sein, so dass es Zeit gibt, die Regel zu ändern, wenn meine Interpretation falsch ist.
Wenn Sie nach einer Bibliothek suchen, die den Wert und die "no-value" -Flag an einem Speicherort effizient packt, empfehle ich compact_optional . Es macht genau das.
Es ist nicht auf boost::optional
oder std::experimental::optional
spezialisiert, aber es kann sie umschließen, so dass Sie eine einheitliche Schnittstelle erhalten, mit Optimierungen wo möglich und einem Fallback auf 'klassische' Option, falls erforderlich.
Ich habe nach dem gleichen gefragt, was die Spezialisierung optional<bool>
und optional<tribool>
neben anderen Beispielen betrifft, um nur ein Byte zu verwenden. Während die "Legalität", solche Dinge zu tun, nicht diskutiert wurde, denke ich, dass es theoretisch nicht erlaubt sein sollte, optional<T>
im Gegensatz zu zB .: hash (was explizit erlaubt ist) zu spezialisieren.
Ich habe die Protokolle nicht bei mir, aber ein Teil der Begründung ist, dass die Schnittstelle den Zugriff auf die Daten als Zugriff auf einen Zeiger oder Verweis behandelt, was bedeutet, dass wenn Sie eine andere Datenstruktur in den Interna verwenden, einige der Invarianten des Zugriffs können sich ändern; ganz zu schweigen von der Bereitstellung der Schnittstelle mit Zugriff auf die Daten könnte etwas wie reinterpret_cast<(some_reference_type)>
erfordern. Die Verwendung von uint8_t
zum Speichern eines optionalen Bool würde beispielsweise mehrere zusätzliche Anforderungen an die Schnittstelle von optional<bool>
stellen, die sich von denen von optional<T>
unterscheiden. Wie lautet der Rückgabetyp von operator*
?
Im Grunde glaube ich, dass die Idee darin besteht, das ganze vector<bool>
fiasco wieder zu vermeiden.
In Ihrem Beispiel ist es vielleicht nicht so schlimm, da der Zugriffstyp immer your_integer_type&
(oder Zeiger) ist. Aber in diesem Fall ist es vielleicht die sicherste Wahl, einfach Ihren Integer-Typ so zu gestalten, dass er einen "Zombie" oder "unbestimmten" Wert anstelle von optional<>
für den Job mit seinem zusätzlichen Overhead und den zusätzlichen Anforderungen bietet.
Ich sehe nicht, wie das Zulassen oder Nicht-Erlauben eines bestimmten Bitmusters, den nicht eingreifenden Zustand darzustellen, unter alles fällt, was der Standard abdeckt.
Wenn Sie versuchen, einen Bibliothekshersteller zu überzeugen, dies zu tun, würde es eine Implementierung erfordern, erschöpfende Tests, um zu zeigen, dass Sie nicht versehentlich die Anforderungen von optionalem (oder versehentlich aufgerufenem undefiniertem Verhalten) und umfassendem Benchmarking zur Anzeige gebracht haben Dies macht einen erheblichen Unterschied in realen (und nicht nur erfundenen) Situationen.
Natürlich können Sie mit Ihrem eigenen Code machen, was Sie wollen.
Ich habe beschlossen, dass dies eine nützliche Sache ist, aber eine vollständige Spezialisierung ist ein wenig mehr Arbeit als nötig (zum Beispiel, um operator=
korrekt zu bekommen).
Ich habe auf der Boost-Mailingliste eine Möglichkeit zur Vereinfachung der Aufgabe der Spezialisierung angegeben, besonders wenn Sie nur einige Instanziierungen einer Klassenvorlage spezialisieren möchten.
Meine aktuelle Schnittstelle beinhaltet einen speziellen Tag-Typ, der den Zugriff auf bestimmte Funktionen freigibt. Ich habe diesen Typ optional_tag
genannt. Nur optional
kann ein optional_tag
konstruieren. Damit ein Typ sich für eine platzsparende Darstellung anmeldet, benötigt er die folgenden Elementfunktionen:
T(optional_tag)
erstellt einen nicht initialisierten Wert initialize(optional_tag, Args && ...)
konstruiert ein Objekt, wenn bereits eines existiert uninitialize(optional_tag)
zerstört das enthaltene Objekt is_initialized(optional_tag)
prüft, ob sich das Objekt gerade in einem initialisierten Zustand befindet Indem wir immer den Parameter option_tag benötigen, beschränken wir keine Funktionssignaturen. Aus diesem Grund können wir zum Beispiel operator bool()
nicht als Test verwenden, da der Typ diesen Operator aus anderen Gründen benötigt.
Ein Vorteil gegenüber anderen möglichen Implementierungsmethoden ist, dass Sie mit jedem Typ arbeiten können, der einen solchen Zustand natürlich unterstützt. Es fügt keine Anforderungen wie einen Move-Konstruktor hinzu.
Sie können eine vollständige Codeimplementierung der Idee unter
sehenund für eine Klasse, die die Spezialisierung verwendet:
(Zeilen 220 bis 242)
Dies steht im Gegensatz zu meiner vorherigen Implementierung, bei der Benutzer eine Klassenvorlage spezialisieren mussten. Sie können die alte Version hier sehen:
und
Das Problem bei diesem Ansatz ist, dass es einfach mehr Arbeit für den Benutzer ist. Anstatt vier Elementfunktionen hinzuzufügen, muss der Benutzer in einen neuen Namespace gehen und eine Vorlage spezialisieren.
In der Praxis würden alle Spezialisierungen einen in_place_t
-Konstruktor haben, der alle Argumente an den zugrunde liegenden Typ weiterleitet. Der optional_tag
-Ansatz kann andererseits direkt die Konstruktoren des zugrunde liegenden Typs verwenden.
Bei der Spezialisierung optional_storage
ist der Benutzer auch dafür verantwortlich, geeignete referenzenqualifizierte Überladungen einer Wertfunktion hinzuzufügen. In der optional_tag
-Ansatz haben wir bereits den Wert, so dass wir es nicht herausziehen müssen.
optional_storage
erforderte auch die Standardisierung als Teil der Schnittstelle von optionalen zwei Hilfsklassen, von denen nur eine der Benutzer spezialisieren sollte (und manchmal ihre Spezialisierung an die andere delegiert).
compact_optional
ist eine Art zu sagen "Behandle diesen speziellen Wächter-Wert, da der Typ nicht vorhanden ist, fast wie ein NaN". Es erfordert, dass der Benutzer weiß, dass der Typ, mit dem er arbeitet, über einen speziellen Sentinel verfügt. Ein leicht spezialisierbarer optional
ist eine Art zu sagen "Mein Typ benötigt keinen zusätzlichen Speicherplatz um den nicht vorhandenen Zustand zu speichern, aber dieser Zustand ist kein normaler Wert." Es ist nicht erforderlich, dass jemand über die Optimierung Bescheid weiß, um davon profitieren zu können. Jeder, der den Typ benutzt, bekommt es kostenlos.
Mein Ziel ist es, dies zuerst in boost :: optional und dann in den optionalen std :: optional zu bringen. Bis dahin können Sie immer bounded::optional
verwenden, obwohl es einige andere (beabsichtigte) Schnittstellenunterschiede gibt.
Tags und Links c++ c++14 template-specialization std