Istnieje wiele przydatnych funkcji <algorithm>
, ale wszystkie działają na „sekwencjach” - parach iteratorów. Na przykład, jeśli mam kontener i lubię na nim biegać std::accumulate
, muszę napisać:
std::vector<int> myContainer = ...;
int sum = std::accumulate(myContainer.begin(), myContainer.end(), 0);
Gdy wszystko, co zamierzam zrobić, to:
int sum = std::accumulate(myContainer, 0);
Co jest w moich oczach nieco bardziej czytelne i wyraźniejsze.
Teraz widzę, że mogą zdarzyć się przypadki, w których chciałbyś operować tylko na częściach kontenera, więc zdecydowanie warto mieć opcję przekazywania zakresów. Ale przynajmniej z mojego doświadczenia to rzadki wyjątkowy przypadek. Zazwyczaj będę chciał operować na całych kontenerach.
Łatwo jest napisać funkcję otoki, który trwa do pojemnika i wywołuje begin()
i end()
na nim, ale takie funkcje convenience nie są zawarte w standardowej bibliotece.
Chciałbym poznać uzasadnienie tego wyboru projektu STL.
źródło
boost::accumulate
Odpowiedzi:
Może to być rzadki szczególny przypadek z twojego doświadczenia , ale w rzeczywistości cały pojemnik jest szczególnym przypadkiem, a arbitralny zasięg jest przypadkiem ogólnym.
Zauważyłeś już, że możesz zaimplementować całą skrzynkę kontenera za pomocą bieżącego interfejsu, ale nie możesz tego zrobić.
Tak więc autor biblioteki miał wybór między implementacją dwóch interfejsów z góry, a tylko implementacją jednego, który wciąż obejmuje wszystkie przypadki.
To prawda, zwłaszcza, że darmowe funkcje
std::begin
istd::end
są teraz włączone.Powiedzmy, że biblioteka zapewnia przeciążenie wygody:
teraz musi również zapewnić równoważne przeciążenie, biorąc funktor porównawczy, i musimy zapewnić odpowiedniki dla każdego innego algorytmu.
Ale przynajmniej objęliśmy każdą sprawę, w której chcemy operować na pełnym kontenerze, prawda? Cóż, niezupełnie. Rozważać
Jeśli chcemy poradzić sobie z operacjami wstecznymi na kontenerach, potrzebujemy innej metody (lub pary metod) dla każdego istniejącego algorytmu.
Tak więc podejście oparte na zakresie jest bardziej ogólne w prostym sensie, że:
Oczywiście istnieje jeszcze jeden ważny powód, który polegał na tym, że standaryzacja STL była już bardzo pracochłonna, a nadmuchiwanie go wygodnymi opakowaniami, zanim został on szeroko użyty, nie byłby świetnym wykorzystaniem ograniczonego czasu komitetu. Jeśli jesteś zainteresowany, możesz znaleźć raport techniczny Stepanova i Lee tutaj
Jak wspomniano w komentarzach, Boost.Range zapewnia nowsze podejście bez konieczności wprowadzania zmian w standardzie.
źródło
f(c.begin(), c.end(), ...)
, a być może tylko najczęściej używane przeciążenie (jakkolwiek to określisz), aby zapobiec podwojeniu liczby przeciążeń. Ponadto adaptery iteratorów są całkowicie ortogonalne (jak zauważasz, działają dobrze w Pythonie, których iteratory działają bardzo inaczej i nie mają większości mocy, o której mówisz).std::sort(std::range(start, stop))
.#define MAKE_RANGE(container) (container).begin(), (container).end()
</jk>Okazuje się, że jest artykuł autorstwa Herb Sutter na ten właśnie temat. Zasadniczo problemem jest niejednoznaczność przeciążenia. Biorąc pod uwagę następujące kwestie:
I dodając następujące:
Utrudni to prawidłowe
4
i1
prawidłowe rozróżnienie .Pojęcia zaproponowane, ale ostatecznie nieuwzględnione w C ++ 0x, rozwiązałyby to, a także można je obejść za pomocą
enable_if
. W przypadku niektórych algorytmów nie stanowi to żadnego problemu. Ale zdecydowali się tego nie robić.Teraz, po przeczytaniu wszystkich komentarzy i odpowiedzi tutaj, myślę, że
range
obiekty byłyby najlepszym rozwiązaniem. Myślę, że popatrzęBoost.Range
.źródło
typename Iter
wydaje się zbyt typowe dla zbyt ścisłego języka. Wolałbym nptemplate<typename Container> void sort(typename Container::iterator, typename Container::iterator); // 1
itemplate<template<class> Container, typename T> void sort( Container<T>&, std::function<bool(const T&)> ); // 4
inne (które może rozwiązać problem jednoznaczne)T[]::iterator
dostępne. Ponadto właściwy iterator nie musi być zagnieżdżonym typem żadnej kolekcji, wystarczy zdefiniowaćstd::iterator_traits
.Zasadniczo stara decyzja. Koncepcja iteratora jest wzorowana na wskaźnikach, ale pojemniki nie są modelowane na tablicach. Co więcej, ponieważ tablice są trudne do przekazania (ogólnie potrzeba nieparametrowego parametru szablonu), dość często funkcja ma dostępne tylko wskaźniki.
Ale tak, z perspektywy czasu decyzja jest zła. Lepiej byłoby nam, gdyby obiekt zasięgu można było zbudować z jednego
begin/end
lub drugiegobegin/length
; teraz_n
zamiast tego mamy wiele sufiksowanych algorytmów.źródło
Dodanie ich nie przyniesie ci żadnej mocy (możesz już zrobić cały kontener, dzwoniąc
.begin()
i.end()
sam), i doda jeszcze jedną rzecz do biblioteki, która musi być odpowiednio określona, dodana do bibliotek przez dostawców, przetestowana, utrzymana, itd itd.Krótko mówiąc, prawdopodobnie nie ma go tam, ponieważ nie warto zadbać o utrzymanie zestawu dodatkowych szablonów, aby uratować użytkowników z całego kontenera przed wpisaniem jednego dodatkowego parametru wywołania funkcji.
źródło
std::getline
i nadal jest w bibliotece. Można posunąć się tak daleko, że można powiedzieć, że rozbudowane struktury kontroli nie zyskują mojej mocy, ponieważ mogłem zrobić wszystko używając tylkoif
igoto
. Tak, niesprawiedliwe porównanie, wiem;) Wydaje mi się, że rozumiem jakoś specyfikację / implementację / konserwację, ale mówimy tu tylko o małym opakowaniu, więc ..Do tej pory http://en.wikipedia.org/wiki/C++11#Range-based_for_loop jest dobrą alternatywą dla
std::for_each
. Zauważ, że nie ma wyraźnych iteratorów:(Zainspirowany https://stackoverflow.com/a/694534/2097284 .)
źródło
<algorithm>
, a nie wszystkie potrzebne algobegin
iend
iteratory - ale korzyści nie można przecenić! Kiedy po raz pierwszy wypróbowałem C ++ 03 w 2009ish, unikałem iteratorów ze względu na płytę zapętloną i na szczęście moje projekty w tym czasie na to pozwoliły. Ponowne uruchomienie na C ++ 11 w 2014 roku było niesamowitym ulepszeniem, język zawsze powinien być w C ++, a teraz nie mogę bez niego żyćauto &it: them
:)