Posso restituire un piping temporaneo a un'operazione di intervallo?


9

Supponiamo che io abbia una generate_my_rangeclasse che modella un range(in particolare, lo è regular). Quindi il codice seguente è corretto:

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>;

Viene my_custom_rng_gen(some_param)preso in base al valore dal (primo) operatore di conduttura o ho un riferimento penzolante dopo aver lasciato l' generate_my_rangeambito?

Sarebbe lo stesso con la chiamata funzionale ranges::views::transform(my_custom_rng_gen(some_param),my_transform_op)?

Sarebbe corretto se usassi un riferimento al valore? per esempio:

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

Se gli intervalli sono presi da valori per queste operazioni, cosa devo fare se passo un riferimento di valore a un contenitore? Dovrei usare un ranges::views::all(my_container)modello?


My_custom_rng_gen (some_param) è già limitato? Intendi qualcosa come godbolt.org/z/aTF8RN senza il take (5)?
Porsche9II

@ Porsche9II Sì, questa è una gamma limitata. Diciamo che è un container
Bérenger il

Risposte:


4

Nella libreria degli intervalli ci sono due tipi di operazioni:

  • viste pigre e che richiedono l'esistenza del contenitore sottostante.
  • azioni che sono desiderose e di conseguenza producono nuovi contenitori (o modificano quelli esistenti)

Le viste sono leggere. Li passi per valore e richiedi che i contenitori sottostanti rimangano validi e invariati.

Dalla documentazione di range-v3

Una vista è un wrapper leggero che presenta una vista di una sequenza di elementi sottostante in qualche modo personalizzato senza modificarla o copiarla. Le viste sono economiche da creare e copiare e hanno una semantica di riferimento non proprietaria.

e:

Qualsiasi operazione sull'intervallo sottostante che invalida i suoi iteratori o sentinelle invaliderà anche qualsiasi vista che si riferisce a qualsiasi parte di tale intervallo.

La distruzione del contenitore sottostante invalida ovviamente tutti gli iteratori ad esso.

Nel tuo codice stai specificatamente usando le viste - Tu usi ranges::views::transform. La pipa è semplicemente uno zucchero sintattico per facilitare la scrittura così com'è. Dovresti guardare l'ultima cosa nella pipa per vedere cosa produci - nel tuo caso, è una vista.

Se non ci fosse un operatore di tubi, probabilmente sarebbe simile a questo:

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

se ci fossero più trasformazioni collegate in questo modo puoi vedere quanto sarebbe brutto.

Pertanto, se my_custom_rng_genproduce un tipo di contenitore, che trasformi e poi ritorni, quel contenitore viene distrutto e hai riferimenti penzolanti dalla tua vista. Se my_custom_rng_genè un'altra visione di un contenitore che vive al di fuori di questi ambiti, tutto va bene.

Tuttavia, il compilatore dovrebbe essere in grado di riconoscere che stai applicando una vista su un contenitore temporaneo e colpirti con un errore di compilazione.

Se si desidera che la funzione restituisca un intervallo come contenitore, è necessario "materializzare" esplicitamente il risultato. Per questo, utilizzare l' ranges::tooperatore all'interno della funzione.


Aggiornamento: per essere più esplicito riguardo al tuo commento "dove dice la documentazione che comporre range / piping prende e memorizza una vista?"

Pipe è semplicemente uno zucchero sintattico per connettere le cose in un'espressione di facile lettura. A seconda di come viene utilizzato, può o meno restituire una vista. Dipende dall'argomento sul lato destro. Nel tuo caso è:

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

Quindi l'espressione restituisce qualunque cosa views::transformritorni.

Ora, leggendo la documentazione della trasformazione:

Di seguito è riportato un elenco dei combinatori o viste di intervallo pigro forniti da Range-v3 e un blurb su come ciascuno di essi deve essere utilizzato.

[...]

views::transform

Dato un intervallo di origine e una funzione unaria, restituisce un nuovo intervallo in cui ciascun elemento risultato è il risultato dell'applicazione della funzione unaria a un elemento sorgente.

Quindi restituisce un intervallo, ma poiché è un operatore pigro, quell'intervallo che restituisce è una vista, con tutta la sua semantica.


Ok. Ciò che è un po 'misterioso per me è ancora come funziona quando passo un contenitore nella pipe (ovvero l'oggetto range creato dalla composizione). Deve in qualche modo memorizzare una vista del contenitore. È finito ranges::views::all(my_container)? E se una vista viene passata alla pipe? Riconosce che è passato un contenitore o una vista? È necessario? Come?
Bérenger

"Il compilatore dovrebbe essere in grado di riconoscere che stai applicando una vista su un contenitore temporaneo e colpirti con un errore di compilazione" Questo è quello che ho pensato anche io: se faccio qualcosa di stupido, significa un contratto sul tipo (essere una sinistra valore) non è soddisfatto. Cose del genere sono fatte da range-v3. Ma in questo caso non c'è assolutamente nessun problema. Compila e gira. Quindi potrebbe esserci un comportamento indefinito, ma non si presenta.
Bérenger

Per essere sicuro se il tuo codice viene eseguito per errore o se tutto va bene, dovrei vedere il contenuto di my_custom_rng_gen. Come esattamente il tubo e transforminteragire sotto il cofano non è importante. L'intera espressione accetta un intervallo come argomento (un contenitore o una vista in un contenitore) e restituisce una vista diversa a quel contenitore. Il valore restituito non sarà mai proprietario del contenitore, perché è una vista.
CygnusX1

1

Tratto dalla documentazione Range-V3 :

Le viste [...] hanno una semantica di riferimento non proprietaria.

e

Avere un oggetto a intervallo singolo consente pipeline di operazioni. In una pipeline, un intervallo viene pigramente adattato o mutato con entusiasmo in qualche modo, con il risultato immediatamente disponibile per un ulteriore adattamento o mutazione. L'adattamento pigro è gestito dalle viste e la mutazione avida è gestita dalle azioni.

// 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"};

Nel codice sopra, rng memorizza semplicemente un riferimento ai dati sottostanti e alle funzioni di filtro e trasformazione. Non viene eseguito alcun lavoro fino a quando non viene ripetuto rng.

Dato che hai detto che l'intervallo temporaneo può essere pensato come un contenitore, la tua funzione restituisce un riferimento penzolante.

In altre parole, devi assicurarti che l'intervallo sottostante sopravviva alla vista o che tu sia nei guai.


Sì, le viste non sono di proprietà, ma dove la documentazione dice che comporre range / piping prende e memorizza una vista? Sarebbe possibile (e penso che sia una buona cosa) avere la seguente politica: memorizzare per valore se l'intervallo è dato da un riferimento di valore.
Bérenger

1
@ Bérenger ho aggiunto un po 'di più dalla documentazione delle gamme. Ma il punto è davvero: una visione non è proprietaria . Non importa se gli dai un valore.
Rumburak,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.