Dlaczego wektor libc ++ <bool> :: const_reference nie jest bool?

92

Sekcja 23.3.7 Klasa vector<bool>[vector.bool], akapit 1 stwierdza:

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

Jednak ten program nie kompiluje się podczas korzystania z libc ++:

#include <vector>
#include <type_traits>

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

Ponadto zauważam, że standard C ++ był spójny w tej specyfikacji aż do C ++ 98. Ponadto zauważam, że libc ++ konsekwentnie nie przestrzega tej specyfikacji od pierwszego wprowadzenia libc ++.

Jaka jest przyczyna tego braku zgodności?

Howard Hinnant
źródło

Odpowiedzi:

99

Motywacją do tego rozszerzenia, które jest wykrywalne przez zgodny program, a zatem niezgodne, jest sprawienie, aby vector<bool>zachowywały się bardziej jakvector<char> w odniesieniu do odniesień (stałych i innych).

Wprowadzenie

Od 1998 roku vector<bool>był wyśmiewany jako „niezupełnie pojemnik”. LWG 96 , jeden z pierwszych numerów LWG, zapoczątkował debatę. Dziś, 17 lat później, vector<bool>pozostaje w dużej mierze niezmieniony.

W tym artykule przedstawiono kilka konkretnych przykładów, w jaki sposób zachowanie vector<bool>różni się od wszystkich innych instancji programu vector, szkodząc w ten sposób kodowi ogólnemu. Jednak ten sam artykuł szczegółowo omawia bardzo dobre właściwości vector<bool>użytkowe, jeśli są odpowiednio wdrożone.

Podsumowanie : vector<bool>nie jest to zły pojemnik. Właściwie to jest całkiem przydatne. Po prostu ma złe imię.

Wrócić do const_reference

Jak przedstawiono powyżej i szczegółowo omówiono tutaj , wadą jest vector<bool>to, że w kodzie ogólnym zachowuje się inaczej niż inne vectorinstancje. Oto konkretny przykład:

#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);
}

Standardowa specyfikacja mówi, że zaznaczone potwierdzenie // Fires!zostanie wyzwolone, ale tylko wtedy, gdy testzostanie uruchomione z rozszerzeniem vector<bool>. Po uruchomieniu z vector<char>(lub dowolny vectoroprócz boolgdy odpowiednie non-defaultT przypisano ), test przechodzi.

Implementacja libc ++ starała się zminimalizować negatywne skutki vector<bool>odmiennego zachowania w kodzie ogólnym. Jedna rzecz to robiło do osiągnięcia tego celu jest, aby vector<T>::const_referencedo proxy odniesienia , podobnie jak podano vector<T>::reference, oprócz tego, że nie można przypisać jego pośrednictwem. Oznacza to, że w libc ++ vector<T>::const_referencejest zasadniczo wskaźnikiem do bitu wewnątrzvector , zamiast kopii tego bitu.

W libc ++ powyższe działa testzarówno dla, jak vector<char>i vector<bool>.

Jakim kosztem?

Wadą jest to, że to rozszerzenie jest wykrywalne, jak pokazano w pytaniu. Jednak bardzo niewiele programów faktycznie dba o dokładny typ tego aliasu, a więcej programów przejmuje się zachowaniem.

Jaka jest przyczyna tego braku zgodności?

Aby zapewnić klientowi libc ++ lepsze zachowanie w kodzie ogólnym i być może po wystarczających testach w terenie, zaproponuj to rozszerzenie do przyszłego standardu C ++ w celu ulepszenia całej branży C ++.

Taka propozycja może przyjść w postaci nowego kontenera (np. bit_vector), Który ma prawie takie samo API jak dzisiejsze vector<bool>, ale z kilkoma aktualizacjami, takimi jak const_referenceomówione tutaj. Następnie wycofanie (i ostateczne usunięcie) vector<bool>specjalizacji. bitsetprzydałoby się też trochę aktualizacji w tym dziale, np. dodawanie const_referencei zestaw iteratorów.

Oznacza to, że z perspektywy czasu bitsetjest to vector<bool>(co powinno zostać przemianowane na bit_vector- lub cokolwiek innego), tak jak arrayjest vector. A analogia powinna uznać za prawdziwe, czy nie mówimy booljak value_typeo vectoriarray .

Istnieje wiele przykładów funkcji C ++ 11 i C ++ 14, które zaczęły się jako rozszerzenia w libc ++. Tak ewoluują standardy. Faktycznie wykazane pozytywne doświadczenia terenowe mają silny wpływ. Standardowcy to konserwatywna grupa, jeśli chodzi o zmianę istniejących specyfikacji (tak, jak powinny). Zgadywanie, nawet jeśli masz pewność, że zgadujesz poprawnie, jest ryzykowną strategią ewolucji międzynarodowego standardu.

Howard Hinnant
źródło
1
Pytanie: czy ostatni projekt propozycji iteratorów proxy autorstwa @EricNiebler mógłby w jakiś sposób legitymizować rozszerzenia libc ++ i postawić vector<bool>na bardziej pierwszorzędny charakter?
TemplateRex,
Uwaga: wolałbym, aby a vector_bool<Alloc>i an array_bool<N>reprezentowały spakowane wersje (w tym iteratory proxy o swobodnym dostępie iterujące wszystkie bity) vector<bool, Alloc>i array<bool, N>. Jednak bitset<N>(i jego kuzyn boost::dynamic_bitset<Alloc>) reprezentują inną abstrakcję: a mianowicie spakowane wersje std::set<int>. Więc chciałbym mieć, powiedzmy, bit_array<N>i bit_vector<Alloc>być następcami serii bitów, z odpowiednimi iteratorami dwukierunkowymi (iteracja po 1-bitach, a nie po wszystkich bitach). Co o tym myślisz?
TemplateRex,
5
Mój projekt propozycji iteratorów proxy utworzyłby vector<bool>kontener o dostępie swobodnym zgodny ze standardem standardowym. To nie byłby vector<bool>dobry pomysł. :-) Zgadzam się z Howardem. Powinien był nazywać się czymś innym.
Eric Niebler
1
Dlaczego nie ma sposobu, aby zrezygnować z rozszerzeń libc ++ i uzyskać ściśle zgodne zachowanie? (Nie proszę nawet o uczynienie zgodności domyślną, tylko sposób na wyłączenie rozszerzeń libc ++, aby móc pisać przenośny kod). Jak wiecie, w przeszłości ugryzły mnie rozszerzenia krotek libc ++, a ostatnio ugryzły mnie rozszerzenie bitset :: const_reference.
gnzlbg
5
@gnzlbg: Skończona ilość zasobów ekonomicznych i czasowych była dostępna dla początkowego rozwoju libc ++. Później wdrożenie było skazane na to, aby nie uszczęśliwić każdego użytkownika. Biorąc pod uwagę dostępne zasoby, dokonano kompromisów inżynierskich, aby zmaksymalizować liczbę zadowolonych użytkowników, zmaksymalizować korzyści dla całej społeczności C ++. Przepraszam za twoje doświadczenie. Zauważam, że rozszerzenia krotek, z którymi się spotkałeś, znajdują się teraz w bieżącym dokumencie roboczym C ++ 1z. W tej kwestii nieświadomie poświęciłeś się, aby wielu mogło skorzystać, a ci wielu są ci winni dług wdzięczności.
Howard Hinnant,