Czy C ++ 11 zezwala na wektor <const T>?

82

Wymagania dotyczące kontenerów zostały zmienione z C ++ 03 na C ++ 11. Podczas gdy C ++ 03 miał ogólne wymagania (np. Kopiowalność konstruowalności i przypisywalność dla wektora), C ++ 11 definiuje szczegółowe wymagania dotyczące każdej operacji kontenera (sekcja 23.2).

W rezultacie możesz np. Przechowywać w wektorze typ, który można kopiować, ale nie można go przypisać - na przykład strukturę ze stałym składnikiem - o ile wykonujesz tylko określone operacje, które nie wymagają przypisania (konstrukcja i push_backsą takimi operacjami ; insertnie).

Zastanawiam się: czy to oznacza, że ​​teraz pozwala na to standard vector<const T>? Nie widzę żadnego powodu, dla którego nie powinien - const Tpodobnie jak struktura ze składową stałą jest typem, który można kopiować, ale nie można go przypisać - ale być może coś przeoczyłem.

(Część tego, co sprawia, że ​​myślę, że mogłem coś przeoczyć, jest to, że bagażnik gcc ulega awarii i pali się, jeśli próbujesz utworzyć instancję vector<const T>, ale jest w porządku, gdy vector<T>T ma element const).

HighCommander4
źródło

Odpowiedzi:

55

Nie, uważam, że wymagania dotyczące alokatora mówią, że T może być „niebędącym stałym, niereferencyjnym typem obiektu”.

Nie byłbyś w stanie wiele zrobić z wektorem stałych obiektów. A i const vector<T>tak byłby prawie taki sam.


Wiele lat później ta szybka i nieprzyzwoita odpowiedź nadal wydaje się przyciągać komentarze i głosy. Nie zawsze w górę. :-)

Aby dodać odpowiednie odniesienia:

Dla standardu C ++ 03, który mam na papierze, Tabela 31 w sekcji [lib.allocator.requirements] mówi:

T, U any type

Nie żeby jakikolwiek typ faktycznie działał.

Tak więc następny standard, C ++ 11, mówi w zamkniętym szkicu w [Alokator.requirements], a teraz Tabela 27:

T, U, C any non-const, non-reference object type

co jest bardzo bliskie temu, co napisałem powyżej z pamięci. O to też chodziło w tym pytaniu.

Jednak w C ++ 14 ( wersja robocza N4296 ) Tabela 27 mówi teraz:

T, U, C any non-const object type

Być może dlatego, że odwołanie może w końcu nie jest typem obiektu?

A teraz w C ++ 17 ( wersja robocza N4659 ) jest to Tabela 30, która mówi:

T, U, C any cv-unqualified object type (6.9)

Więc nie tylko jest constwykluczone, ale także volatile. Prawdopodobnie i tak stare wiadomości i tylko wyjaśnienie.


Zobacz także informacje z pierwszej ręki Howarda Hinnanta , aktualnie poniżej.

Bo Persson
źródło
43
Konkluzja: Nie zaprojektowaliśmy pojemników do przechowywania const T. Chociaż zastanawiałem się nad tym. I byliśmy naprawdę blisko zrobienia tego przez przypadek. O ile mi wiadomo, aktualnym punktem spornym jest para przeciążonych addressfunkcji składowych w domyślnym alokatorze: gdy T jest stała, te dwa przeciążenia mają ten sam podpis. Łatwym sposobem naprawienia tego byłoby wyspecjalizowanie się std::allocator<const T>i usunięcie jednego z przeciążeń.
Howard Hinnant,
4
@ HighCommander4: Nie jestem pewien. Na libc ++ mogę zbudować wektor (nawet niepusty) z kooperatywnym alokatorem. Nie mogę z tym zrobić nic innego (bez stałego). Nie jestem pewien, czy pasuje to do Twojej definicji „dzieł”. Nie jestem też pewien, czy nieświadomie korzystam z rozszerzenia. Aby się upewnić, musiałbym poświęcić dużo więcej czasu na to pytanie. Już wcześniej dokonałem takiej inwestycji, ale to było kilka lat temu i w międzyczasie wiele się zmieniło. Jeśli to zadziała, to nie jest to zamierzone ze strony komitetu.
Howard Hinnant,
1
@Howard: Nie przychodzi mi do głowy żadna przeszkoda techniczna, aby to zrobić push_back. Ale jeśli nie pozwala na to projekt, lepiej tego nie robić. Byłem tylko ciekawy.
HighCommander4
8
Pamięć podręczna jest często zmiennym kontenerem niezmiennych obiektów, a posortowany wektor jest często alternatywą dla mapy, więc nie zgadzam się, że wektor obiektów stałych jest mało przydatny.
Chris Oldwood,
9
Szkoda. Używałem std::vector<const T>dokładnie dlatego, że jest bardzo podobny do const std::vector<T>, ale bez negatywnych konsekwencji tego ostatniego dla klasy, która go trzyma. W rzeczywistości std::vector<const T>jest DOKŁADNIE tym, czego potrzebuję semantycznie w większości przypadków, w których używam vector. Teraz muszę rzucić const- wraz z niezawodnością, jaką zapewnia.
Violet Giraffe
30

Aktualizacja

Pod zaakceptowaną (i poprawną) odpowiedzią skomentowałem w 2011 roku:

Konkluzja: Nie zaprojektowaliśmy pojemników do przechowywania const T. Chociaż trochę się nad tym zastanowiłem. I byliśmy naprawdę blisko zrobienia tego przez przypadek. Zgodnie z moją najlepszą wiedzą, obecnym punktem spornym jest para przeciążonych addressfunkcji składowych w domyślnym alokatorze: Kiedy Tjest const, te dwa przeciążenia mają ten sam podpis. Łatwym sposobem naprawienia tego byłoby wyspecjalizowanie się std::allocator<const T>i usunięcie jednego z przeciążeń.

Wydaje mi się, że w nadchodzącej wersji roboczej C ++ 17 zalegalizowaliśmy vector<const T>i uważam, że zrobiliśmy to przypadkowo . :-)

P0174R0 usuwa addressprzeciążenia z std::allocator<T>. P0174R0 nie wspomina o wsparciu std::allocator<const T>jako części uzasadnienia.

Korekta

W komentarzach poniżej TC słusznie zauważa, że addressprzeciążenia są przestarzałe , a nie usuwane. Mój błąd. Nieaktualni członkowie nie pojawiają się w 20.10.9, gdzie std::allocatorzdefiniowano, ale zamiast tego są przenoszeni do sekcji D.9. Kiedy to opublikowałem, zaniedbałem przeglądanie rozdziału D pod kątem takiej możliwości.

Dziękuję TC za korektę. Zastanawiałem się nad usunięciem tej mylącej odpowiedzi, ale być może najlepiej zostawić ją z tą poprawką, aby być może uchroni to kogoś przed błędnym odczytaniem specyfikacji w taki sam sposób, jak ja.

Howard Hinnant
źródło
7
To całkiem zabawne! (Teraz musimy być naprawdę cicho i pozwolić mu wślizgnąć się do C ++ 17 bez nikogo zauważenia :))
HighCommander4
Czy tabela wymagań alokatora nie jest nadal po prostu zakazana? Niezależnie od tego, P0174R2 (co jest rewizja głosowało) tylko deprecates, nie usunie address.
TC
@TC: Masz całkowitą rację. Dziękuję za poprawienie mnie.
Howard Hinnant
Więc c ++ 2x wreszcie pozwoli vector<const T>:)
MM
1
Odpowiedź „Konkluzja: nie zaprojektowaliśmy kontenerów, aby pomieścić const T” zakłada, że ​​celem jest, aby kontener miał „const T”. Można jednak argumentować, że celem użytkownika jest ograniczenie operacji na kontenerze - tak, aby np. „Back ()” zwracało „const T &” - niezależnie od tego, co zawiera kontener.
Hans Olsson,
8

Chociaż mamy już bardzo dobre odpowiedzi na ten temat, zdecydowałem się przedstawić bardziej praktyczną odpowiedź, aby pokazać, co można, a czego nie można zrobić.

Więc to nie działa:

vector<const T> vec; 

Po prostu przeczytaj pozostałe odpowiedzi, aby zrozumieć, dlaczego. Jak można się domyślić, to też nie zadziała:

vector<const shared_ptr<T>> vec;

Tjuż nie jest const, ale vectortrzyma shared_ptrs, nie Ts.

Z drugiej strony, to robi pracę:

vector<const T *> vec;
vector<T const *> vec;  // the same as above

Ale w tym przypadku const jest wskazywanym obiektem, a nie samym wskaźnikiem (który jest tym, co przechowuje wektor). Byłoby to równoważne z:

vector<shared_ptr<const T>> vec;

Co jest w porządku.

Ale jeśli umieścimy constna końcu wyrażenia, zmieni on teraz wskaźnik w a const, więc następujące elementy nie zostaną skompilowane:

vector<T * const> vec;

Zgadzam się, trochę zagmatwane, ale przyzwyczajasz się do tego.

Lucio Paiva
źródło
4

Uzupełniając inne odpowiedzi, innym podejściem jest użycie:

vector<unique_ptr<const T>> vec;

Jeśli jest to przypadek, w którym chcesz wyegzekwować, że vecma tylko własność swoich elementów. Lub jeśli chcesz dynamicznie przenosić elementy do veci w pewnym momencie je przenosić.

Jak podkreślił, wskaźnik constsemantyka może być mylące, ale shared_ptri unique_ptrnie są. const unique_ptr<T>jest wskaźnikiem do stałej i unique_ptr<const T>jest punktem stałym, jak można się spodziewać.

Daniel Gouvêa
źródło