Warum ist der Vektor <bool> :: const_reference von libc ++ nicht bool?

92

In Abschnitt 23.3.7 Klasse vector<bool>[vector.bool], Absatz 1 heißt es:

template <class Allocator> class vector<bool, Allocator> {
public:
    // types:
    typedef bool              const_reference;
    ...

Dieses Programm kann jedoch bei Verwendung von libc ++ nicht kompiliert werden:

#include <vector>
#include <type_traits>

int
main()
{
    static_assert(std::is_same<std::vector<bool>::const_reference, bool>{}, "?");
}

Außerdem stelle ich fest, dass der C ++ - Standard in dieser Spezifikation bis in C ++ 98 konsistent war. Außerdem stelle ich fest, dass libc ++ dieser Spezifikation seit der ersten Einführung von libc ++ konsequent nicht mehr gefolgt ist.

Was ist die Motivation für diese Nichtkonformität?

Howard Hinnant
quelle

Antworten:

99

Die Motivation für diese Erweiterung, die von einem konformen Programm erkannt werden kann und daher nicht konform ist, besteht darin, das vector<bool>Verhalten vector<char>in Bezug auf Referenzen (const und anders) ähnlicher zu machen .

Einführung

Seit 1998 vector<bool>wird als "nicht ganz ein Container" verspottet. LWG 96 , eine der allerersten LWG-Fragen, leitete die Debatte ein. Heute, 17 Jahre später, vector<bool>bleibt weitgehend unverändert.

In diesem Artikel werden einige spezifische Beispiele dafür vorgestellt, wie sich das Verhalten von vector<bool>von jeder anderen Instanziierung von unterscheidet vectorund somit generischen Code verletzt. In demselben Artikel werden jedoch ausführlich die sehr guten Leistungseigenschaften erörtert, vector<bool>die bei ordnungsgemäßer Implementierung erzielt werden können.

Zusammenfassung : vector<bool>ist kein schlechter Container. Es ist eigentlich sehr nützlich. Es hat nur einen schlechten Ruf.

Zurück zu const_reference

Wie oben eingeführt und hier detailliert beschrieben , ist das Schlechte daran, vector<bool>dass es sich im generischen Code anders verhält als andere vectorInstanziierungen. Hier ist ein konkretes Beispiel:

#include <cassert>
#include <vector>

template <class T>
void
test(std::vector<T>& v)
{
    using const_ref = typename std::vector<T>::const_reference;
    const std::vector<T>& cv = v;
    const_ref cr = cv[0];
    assert(cr == cv[0]);
    v[0] = 1;
    assert(true == cv[0]);
    assert(cr == cv[0]);  // Fires!
}

int
main()
{
    std::vector<char> vc(1);
    test(vc);
    std::vector<bool> vb(1);
    test(vb);
}

Die Standardspezifikation besagt, dass die markierte // Fires!Zusicherung ausgelöst wird, jedoch nur, wenn testsie mit a ausgeführt wird vector<bool>. Wenn mit einem Lauf vector<char>(oder jede vectoraußer boolwenn ein geeignetes Nicht-Standard Tzugeordnet ist), hat der Test.

Die libc ++ - Implementierung versuchte, die negativen Auswirkungen eines anderen vector<bool>Verhaltens im generischen Code zu minimieren . Eine Sache, die es getan hat, um dies zu erreichen, ist, vector<T>::const_referenceeine Proxy-Referenz zu erstellen , genau wie die angegebene vector<T>::reference, außer dass Sie nicht durch sie zuweisen können. Das heißt, in libc ++ vector<T>::const_referenceist es im Wesentlichen ein Zeiger auf das Bit innerhalb von vector, anstelle einer Kopie dieses Bits.

Unter libc ++ gilt das oben testGesagte sowohl für vector<char>als auch für vector<bool>.

Zu welchem ​​Preis?

Der Nachteil ist, dass diese Erweiterung erkennbar ist, wie in der Frage gezeigt. Allerdings kümmern sich nur sehr wenige Programme tatsächlich um den genauen Typ dieses Alias, und mehr Programme kümmern sich um das Verhalten.

Was ist die Motivation für diese Nichtkonformität?

Um dem libc ++ - Client ein besseres Verhalten im generischen Code zu verleihen und möglicherweise nach ausreichenden Feldtests, schlagen Sie diese Erweiterung für einen zukünftigen C ++ - Standard vor, um die gesamte C ++ - Branche zu verbessern.

Ein solcher Vorschlag könnte in Form eines neuen Containers (z. B. bit_vector) vorliegen, der fast die gleiche API wie heute aufweist vector<bool>, jedoch mit einigen Upgrades wie dem const_referencehier beschriebenen. Gefolgt von einer Abwertung (und eventuellen Entfernung) der vector<bool>Spezialisierung. bitsetkönnte auch ein kleines Upgrade in dieser Abteilung gebrauchen, z. B. Hinzufügen const_referenceund eine Reihe von Iteratoren.

Das heißt, im Nachhinein bitsetist zu vector<bool>(was umbenannt werden sollte bit_vector- oder was auch immer), wie es arrayist vector. Und die Analogie sollte zutreffen, ob wir boolals value_typevon vectorund sprechen oder nicht array.

Es gibt mehrere Beispiele für C ++ 11- und C ++ 14-Funktionen, die als Erweiterungen in libc ++ begonnen haben. So entwickeln sich Standards. Die tatsächlich nachgewiesenen positiven Felderfahrungen haben einen starken Einfluss. Die Leute von Standards sind ein konservativer Haufen, wenn es darum geht, bestehende Spezifikationen zu ändern (wie sie sein sollten). Selbst wenn Sie sicher sind, dass Sie richtig raten, ist das Erraten eine riskante Strategie, um einen international anerkannten Standard zu entwickeln.

Howard Hinnant
quelle
1
Frage: Könnte / würde der jüngste Entwurf eines Vorschlags zu Proxy-Iteratoren von @EricNiebler die libc ++ - Erweiterungen irgendwie legitimieren und vector<bool>eine erstklassigere Grundlage schaffen?
TemplateRex
Anmerkung: Ich würde es vorziehen, ein vector_bool<Alloc>und ein array_bool<N>zu haben, um gepackte Versionen (einschließlich Proxy-Iteratoren mit wahlfreiem Zugriff, die alle Bits iterieren) von vector<bool, Alloc>und darzustellen array<bool, N>. Allerdings bitset<N>(und es ist Cousin boost::dynamic_bitset<Alloc>) stellen eine andere Abstraktion dar: nämlich gepackte Versionen von std::set<int>. Also Ich mag würde haben, zu sagen, bit_array<N>und bit_vector<Alloc>die Nachfolger der bitset Franchise zu sein, mit entsprechenden bidirektionalen Iteratoren (Iterieren über die 1-Bits, anstatt über alle Bits). Was denkst du darüber?
TemplateRex
5
Mein Entwurf eines Vorschlags für Proxy-Iteratoren würde vector<bool>einen std-konformen Container mit wahlfreiem Zugriff erstellen. Es wäre keine vector<bool>gute Idee. :-) Ich stimme Howard zu. Es hätte etwas anderes heißen sollen.
Eric Niebler
1
Warum gibt es keine Möglichkeit, libc ++ - Erweiterungen zu deaktivieren und ein streng konformes Verhalten zu erzielen? (Ich bitte nicht einmal darum, die Konformität zur Standardeinstellung zu machen, sondern nur darum, die Erweiterungen von libc ++ zu deaktivieren, um portablen Code schreiben zu können.) Wie Sie wissen, wurde ich in der Vergangenheit von libc ++ - Tupelerweiterungen und kürzlich von der Erweiterung bitset :: const_reference gebissen.
Gnzlbg
5
@gnzlbg: Für die anfängliche Entwicklung von libc ++ standen eine begrenzte Menge wirtschaftlicher und zeitlicher Ressourcen zur Verfügung. In der Folge war die Implementierung dazu verdammt, nicht jeden einzelnen Benutzer glücklich zu machen. Angesichts der verfügbaren Ressourcen wurden technische Kompromisse eingegangen, um die Anzahl der zufriedenen Benutzer zu maximieren und den Nutzen für die gesamte C ++ - Community zu maximieren. Entschuldigung für Ihre Erfahrung. Ich stelle fest, dass die Tupel-Erweiterungen, gegen die Sie verstoßen haben, jetzt im aktuellen C ++ 1z-Arbeitspapier enthalten sind. In dieser Angelegenheit haben Sie unwissentlich geopfert, damit viele davon profitieren können, und diese vielen schulden Ihnen Dankbarkeit.
Howard Hinnant