Jak wiele osób próbuję obecnie różnych funkcji, które oferuje C ++ 11. Jednym z moich ulubionych jest „oparte na zakresie pętle”.
Rozumiem, że:
for(Type& v : a) { ... }
Jest równa:
for(auto iv = begin(a); iv != end(a); ++iv)
{
Type& v = *iv;
...
}
A to begin()
po prostu zwraca a.begin()
standardowe pojemniki.
Ale co jeśli chcę, aby mój niestandardowy typ „oparty na zakresie dla pętli” był widoczny ?
Czy powinienem się po prostu specjalizować begin()
i end()
?
Jeśli mój niestandardowy typ należy do przestrzeni nazw xml
, czy powinienem zdefiniować xml::begin()
czy std::begin()
?
Krótko mówiąc, jakie są wytyczne, aby to zrobić?
c++
for-loop
c++11
customization
ereOn
źródło
źródło
begin/end
lub przyjaciela, statyczne lub wolnebegin/end
. Uważaj tylko, w której przestrzeni nazw umieścisz darmową funkcję: stackoverflow.com/questions/28242073/...for( auto x : range<float>(0,TWO_PI, 0.1F) ) { ... }
. Jestem ciekawy, jak omijasz fakt, że `operator! = ()` `Jest trudny do zdefiniowania. A co*__begin
w tym przypadku z dereferencing ( )? Myślę, że będzie to wielki wkład, jeśli ktoś pokazał nam, jak to jest zrobione!Odpowiedzi:
Standard został zmieniony, ponieważ pytanie (i większość odpowiedzi) zostało zamieszczone w rezolucji tego raportu o defektach .
Sposób, aby
for(:)
pętla działała na twoim typie,X
jest teraz jednym z dwóch sposobów:Tworzenie elementu
X::begin()
iX::end()
że coś powrotny, który działa jak iteratoraStwórz darmową funkcję
begin(X&)
iend(X&)
że coś powrotny, który działa jak iteratora, w tej samej przestrzeni nazw jako typX
.¹I podobne dla
const
odmian. Będzie to działać zarówno na kompilatorach, które implementują zmiany raportu defektów, jak i na kompilatorach, które tego nie robią.Zwrócone obiekty nie muszą tak naprawdę być iteratorami.
for(:)
Pętli, w przeciwieństwie do większości części C ++ standardowego jest określony rozszerzyć na coś równoważne :staje się:
gdzie zmienne zaczynające się od
__
są tylko dla prezentacjibegin_expr
iend_expr
są magią, która wywołujebegin
/end
.²Wymagania dotyczące
++
początkowej / końcowej wartości zwracanej są proste: należy przeciążać wstępnie , upewnić się, że wyrażenia inicjujące są poprawne, binarne!=
, których można użyć w kontekście logicznym, jednoargumentowe,*
które zwraca coś, co można przypisać-zainicjowaćrange_declaration
, i ujawnić publiczny burzyciel.Robienie tego w sposób niezgodny z iteratorem jest prawdopodobnie złym pomysłem, ponieważ przyszłe iteracje C ++ mogą być względnie nonszalanckie jeśli chodzi o złamanie kodu.
Nawiasem mówiąc, istnieje uzasadnione prawdopodobieństwo, że przyszła zmiana standardu pozwoli
end_expr
na zwrot innego rodzaju niżbegin_expr
. Jest to przydatne, ponieważ pozwala na ocenę „leniwego końca” (jak wykrywanie zerowego zakończenia), którą łatwo zoptymalizować, aby była tak wydajna jak odręcznie napisana pętla C, i inne podobne zalety.¹ Zauważ, że
for(:)
pętle przechowują dowolne pliki tymczasowe wauto&&
zmiennej i przekazują je jako wartość. Nie możesz wykryć, czy iterujesz po tymczasowej (lub innej wartości); takie przeciążenie nie będzie wywoływane przezfor(:)
pętlę. Patrz [stmt.ranged] 1.2-1.3 z n4527.² albo wywołać
begin
/end
lub innej metody ADL tylko odnośników wolnej funkcjibegin
/end
, lub magicznych wsparcia tablicy stylu C. Zauważ, żestd::begin
nie jest wywoływany, chyba żerange_expression
zwraca obiekt typunamespace std
lub zależny od niego.W c ++ 17 wyrażenie dla zakresu zostało zaktualizowane
z typami
__begin
i__end
zostały oddzielone.To pozwala, aby iterator końcowy nie był tego samego typu co start. Twój typ iteratora końcowego może być „wartownikiem”, który obsługuje tylko
!=
typ iteratora początkowego.Praktycznym przykładem tego, dlaczego jest to przydatne, jest to, że twój iterator końcowy może przeczytać „sprawdź,
char*
czy wskazuje, że'0'
”, gdy==
jest tochar*
. Dzięki temu wyrażenie w zakresie C ++ może generować optymalny kod podczas iteracji wchar*
buforze zakończonym zerem.przykład na żywo w kompilatorze bez pełnej obsługi C ++ 17;
for
pętla rozwinięta ręcznie.źródło
begin
iend
funkcji niż jest to dostępne w normalnym kodzie. Być może mogliby być bardzo wyspecjalizowani w zachowaniu się inaczej (tzn. Szybciej, ignorując argument końcowy, aby uzyskać maksymalną optymalizację możliwą). Ale nie jestem wystarczająco dobry w przestrzeniach nazw, aby mieć pewność, jak to zrobić.begin(X&&)
. Tymczasowe jest zawieszone w powietrzuauto&&
w zakresie dla ibegin
zawsze jest wywoływane za pomocą lvalue (__range
).Piszę odpowiedź, ponieważ niektórzy ludzie mogą być bardziej zadowoleni z prostego przykładu z życia bez STL.
Z jakiegoś powodu mam własną prostą implementację tablicy danych i chciałem użyć zakresu opartego na pętli. Oto moje rozwiązanie:
Następnie przykład użycia:
źródło
const
kwalifikator zwrotu dlaconst DataType& operator*()
i pozwolić użytkownikowi wybrać opcjęconst auto&
lubauto&
? W każdym razie dzięki, świetna odpowiedź;)Odpowiednią częścią normy jest 6.5.4 / 1:
Możesz więc wykonać dowolną z następujących czynności:
begin
iend
składać funkcjebegin
iend
zwolnij funkcje, które zostaną znalezione przez ADL (wersja uproszczona: umieść je w tej samej przestrzeni nazw co klasa)std::begin
istd::end
std::begin
i tak wywołuje funkcjębegin()
członka, więc jeśli zaimplementujesz tylko jeden z powyższych, wyniki powinny być takie same bez względu na to, który wybierzesz. To te same wyniki dla pętli opartych na dystansie, a także ten sam wynik dla zwykłego śmiertelnego kodu, który nie ma własnych reguł rozpoznawania magicznych nazw, więcusing std::begin;
następuje po nim niewykwalifikowane wywołaniebegin(a)
.W przypadku zastosowania funkcji członków i funkcji ADL, choć wtedy piecyk oparciu o pętle powinny wywoływać funkcje członków, podczas gdy zwykli śmiertelnicy będą wywoływać funkcje ADL. Najlepiej upewnij się, że robią to samo w takim przypadku!
Jeśli to, co piszesz, implementuje interfejs kontenera, to będzie już miał
begin()
iend()
funkcje składowe, co powinno wystarczyć. Jeśli jest to zakres, który nie jest kontenerem (co byłoby dobrym pomysłem, jeśli jest niezmienny lub jeśli nie znasz rozmiaru z przodu), możesz wybrać.Z opcjami rozplanować, notatkę, która nie musi przeciążenia
std::begin()
. Możesz specjalizować się w standardowych szablonach dla typu zdefiniowanego przez użytkownika, ale oprócz tego dodawanie definicji do przestrzeni nazw std jest nieokreślonym zachowaniem. Ale w każdym razie specjalizacja standardowych funkcji jest złym wyborem, choćby dlatego, że brak częściowej specjalizacji funkcji oznacza, że możesz to zrobić tylko dla jednej klasy, a nie dla szablonu klasy.źródło
!=
, prefiks++
i unary*
. Prawdopodobnie nierozsądne jest wdrażanie funkcji członkowskichbegin()
iend()
funkcji ADL, które zwracają wszystko inne niż iterator, ale myślę, że jest to legalne.std::begin
Myślę, że specjalizacją w zwróceniu nie-iteratora jest UB.O ile mi wiadomo, to wystarczy. Musisz także upewnić się, że zwiększenie wskaźnika będzie miało miejsce od początku do końca.
Następny przykład (brakuje stałej wersji początku i końca) kompiluje się i działa dobrze.
Oto kolejny przykład z funkcjami początku / końca. Oni muszą być w tej samej przestrzeni nazw, jako klasy, z powodu ADL:
źródło
return v + 10
.&v[10]
dereferencje lokalizacji pamięci tuż obok tablicy.Jeśli chcesz poprzeć iterację klasy bezpośrednio za pomocą jej
std::vector
lubstd::map
członka, oto kod do tego:źródło
const_iterator
mogą być dostępne również wauto
(C ++ 11) -Kompatybilny sposób poprzezcbegin
,cend
itpTutaj udostępniam najprostszy przykład tworzenia niestandardowego typu, który będzie działał z „ opartą na zakresie dla pętli ”:
Mam nadzieję, że będzie to pomocne dla początkującego programisty takiego jak ja: p :)
Dziękuję.
źródło
end()
funkcja oczywiście nie wyklucza niewłaściwego miejsca w pamięci, ponieważ zajmuje tylko „adres” tego miejsca w pamięci. Dodanie dodatkowego elementu oznaczałoby, że potrzebujesz więcej pamięci i użycieyour_iterator::end()
w jakikolwiek sposób, który odrzuciłby tę wartość, i tak nie działałoby z innymi iteratorami, ponieważ są one zbudowane w ten sam sposób.return &data[sizeofarray]
IMHO powinno po prostu zwrócić dane adresowe + sizeofarray, ale co wiem,data + sizeofarray
byłby to właściwy sposób na napisanie tego.Odpowiedź Chrisa Redforda działa również dla kontenerów Qt (oczywiście). Oto adaptacja (zauważ, że zwracam
constBegin()
odpowiednio aconstEnd()
z metod const_iterator):źródło
Chciałbym rozwinąć niektóre części odpowiedzi @Steve Jessop, na które początkowo nie rozumiałem. Mam nadzieję, że to pomoże.
https://en.cppreference.com/w/cpp/language/range-for :
W przypadku pętli opartej na zakresie najpierw wybiera się funkcje składowe.
Ale dla
Funkcje ADL są wybierane jako pierwsze.
Przykład:
źródło