Czy mogę zwrócić tymczasowy potok do operacji zasięgu?

9

Załóżmy, że mam generate_my_rangeklasę, która modeluje range(w szczególności jest regular). Czy następujący kod jest poprawny:

auto generate_my_range(int some_param) {    
  auto my_transform_op = [](const auto& x){ return do_sth(x); };
  return my_custom_rng_gen(some_param) | ranges::views::transform(my_transform_op);
}
auto cells = generate_my_range(10) | ranges::to<std::vector>;

Czy jest my_custom_rng_gen(some_param)pobierany przez wartość (pierwszy) operator potoku, czy też mam zwisające odniesienie po opuszczeniu generate_my_rangezakresu?

Czy byłoby tak samo z wywołaniem funkcji ranges::views::transform(my_custom_rng_gen(some_param),my_transform_op)?

Czy poprawne byłoby użycie odniesienia do wartości? na przykład:

auto generate_my_range(int some_param) {
  auto my_transform_op = [](const auto& x){ return do_sth(x); };
  auto tmp_ref = my_custom_rng_gen(some_param);
  return tmp_ref | ranges::views::transform(my_transform_op);
}

Jeśli zakresy są uwzględniane przez wartości dla tych operacji, to co mam zrobić, jeśli przekażę odwołanie do wartości do kontenera? Czy powinienem użyć ranges::views::all(my_container)wzoru?

Bérenger
źródło
Czy my_custom_rng_gen (some_param) jest już ograniczony? Czy masz na myśli coś takiego jak godbolt.org/z/aTF8RN bez take (5)?
Porsche9II
@ Porsche9II Tak, to jest ograniczony zakres. Powiedzmy, że to kontener
Bérenger

Odpowiedzi:

4

W bibliotece zakresów istnieją dwa rodzaje operacji:

  • widoki leniwe i wymagające istnienia podstawowego kontenera.
  • chętne działania, w wyniku których powstają nowe pojemniki (lub modyfikują istniejące)

Widoki są lekkie. Przekazujesz je według wartości i wymagasz, aby kontenery leżące poniżej były ważne i niezmienione.

Z dokumentacji zakresów-v3

Widok jest lekkim opakowaniem, które przedstawia widok sekwencji podstawowych elementów w niestandardowy sposób bez ich mutowania lub kopiowania. Widoki są tanie w tworzeniu i kopiowaniu oraz mają nie-własnościową semantykę odniesienia.

i:

Wszelkie operacje na podstawowym zakresie, które unieważniają jego iteratory lub wartowniki, również unieważniają każdy widok, który odnosi się do dowolnej części tego zakresu.

Zniszczenie leżącego pod nim kontenera oczywiście unieważnia wszystkie iteratory.

W swoim kodzie używasz widoków - korzystasz ranges::views::transform. Fajka jest cukrem syntaktycznym, co ułatwia pisanie takim, jakim jest. Powinieneś spojrzeć na ostatnią rzecz w rurze, aby zobaczyć, co produkujesz - w twoim przypadku jest to widok.

Gdyby nie było operatora potoku, prawdopodobnie wyglądałby mniej więcej tak:

ranges::views::transform(my_custom_rng_gen(some_param), my_transform_op)

gdyby w ten sposób było połączonych wiele transformacji, można zobaczyć, jak brzydko by to zrobiło.

Tak więc, jeśli my_custom_rng_genprodukuje jakiś kontener, który przekształcasz, a następnie powrócisz, kontener ten zostanie zniszczony i będziesz mieć wiszące odniesienia z twojego widoku. Jeśli my_custom_rng_genjest inny widok kontenera, który mieszka poza tymi zakresami, wszystko jest w porządku.

Jednak kompilator powinien być w stanie rozpoznać, że stosujesz widok do tymczasowego kontenera i uderzył cię błąd kompilacji.

Jeśli chcesz, aby funkcja zwróciła zakres jako kontener, musisz jawnie „zmaterializować” wynik. W tym celu użyj ranges::tooperatora w ramach funkcji.


Aktualizacja: Aby uzyskać więcej informacji na temat komentarza „gdzie dokumentacja mówi, że komponowanie zakresu / orurowania bierze i przechowuje widok?”

Fajka jest jedynie cukrem syntaktycznym, który łączy rzeczy w łatwy do odczytania sposób. W zależności od tego, jak jest używany, może on zwrócić widok. To zależy od argumentu po prawej stronie. W twoim przypadku jest to:

`<some range> | ranges::views::transform(...)`

Zatem wyrażenie zwraca cokolwiek, co views::transformzwraca.

Teraz czytając dokumentację transformacji:

Poniżej znajduje się lista leniwych kombiatorów zakresu lub widoków, które zapewnia Range-v3, oraz informacja o tym, jak każdy z nich jest przeznaczony do użycia.

[...]

views::transform

Biorąc pod uwagę zakres źródłowy i funkcję jednoargumentową, zwróć nowy zakres, w którym każdy element wynikowy jest wynikiem zastosowania funkcji jednoargumentowej do elementu źródłowego.

Dlatego zwraca zakres, ale ponieważ jest to operator leniwy, że zakres zwraca się widokiem, ze wszystkimi jego semantyki.

CygnusX1
źródło
Ok. To, co nadal jest dla mnie nieco tajemnicze, to to, jak działa, gdy przekazuję pojemnik do potoku (tj. Obiekt zasięgu utworzony przez kompozycję). Musi jakoś zapisać widok kontenera. Czy to się skończyło ranges::views::all(my_container)? A jeśli widok zostanie przekazany do rury? Czy rozpoznaje, że przekazano mu kontener lub widok? Czy to konieczne? W jaki sposób?
Bérenger
„Kompilator powinien być w stanie rozpoznać, że stosujesz widok do tymczasowego kontenera i uderzył cię błąd kompilacji”. Tak też myślałem: jeśli zrobię coś głupiego, oznacza to kontrakt na ten typ (bycie lewym wartość) nie jest spełniony. Takie rzeczy są wykonywane przez range-v3. Ale w tym przypadku nie ma absolutnie żadnego problemu. Kompiluje ORAZ działa. Może więc występować niezdefiniowane zachowanie, ale się nie pojawia.
Bérenger
Aby upewnić się, czy Twój kod działa przez przypadek poprawnie lub czy wszystko jest w porządku, musiałbym zobaczyć zawartość my_custom_rng_gen. Jak dokładnie rura i transforminterakcja pod maską nie jest ważne. Całe wyrażenie przyjmuje zakres jako argument (kontener lub widok do jakiegoś kontenera) i zwraca inny widok do tego kontenera. Zwracana wartość nigdy nie będzie właścicielem kontenera, ponieważ jest to widok.
CygnusX1
1

Na podstawie dokumentacji zakresów-v3 :

Widoki [...] mają semantykę odniesienia, która nie jest właścicielem.

i

Posiadanie obiektu z jednym zakresem pozwala potokom operacji. W potoku zakres jest leniwie dostosowywany lub chętnie mutowany w jakiś sposób, a wynik jest natychmiast dostępny do dalszej adaptacji lub mutacji. Leniwą adaptacją zajmują się widoki, a gorliwą mutacją - akcje.

// taken directly from the the ranges documentation
std::vector<int> const vi{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
using namespace ranges;
auto rng = vi | views::remove_if([](int i){ return i % 2 == 1; })
              | views::transform([](int i){ return std::to_string(i); });
// rng == {"2","4","6","8","10"};

W powyższym kodzie rng po prostu przechowuje odniesienie do podstawowych danych oraz funkcji filtrowania i transformacji. Żadna praca nie jest wykonywana do momentu iteracji rng.

Ponieważ powiedziałeś, że zakres tymczasowy może być traktowany jako kontener, twoja funkcja zwraca wiszące odniesienie.

Innymi słowy, musisz upewnić się, że podstawowy zakres przeżywa widok, w przeciwnym razie masz kłopoty.

Rumburak
źródło
Tak, widoki nie są własnością, ale gdzie dokumentacja mówi, że komponowanie zakresu / orurowania bierze i przechowuje widok? Byłoby możliwe (i myślę, że dobrze) mieć następującą zasadę: przechowuj według wartości, jeśli zakres jest podany przez odwołanie do wartości.
Bérenger
1
@ Bérenger Dodałem trochę więcej z dokumentacji zakresów. Ale tak naprawdę chodzi o to, że widok nie jest właścicielem . Nie ma znaczenia, czy podasz mu wartość.
Rumburak