Qual è il vantaggio di utilizzare i riferimenti di inoltro nei cicli for basati su intervallo?


115

const auto&sarebbe sufficiente se voglio eseguire operazioni di sola lettura. Tuttavia, mi sono imbattuto in

for (auto&& e : v)  // v is non-const

un paio di volte di recente. Questo mi fa pensare:

È possibile che in alcuni casi oscuri ci sia qualche vantaggio in termini di prestazioni nell'utilizzo di riferimenti di inoltro, rispetto a auto&o const auto&?

( shared_ptrè un sospetto per oscuri casi d'angolo)


Aggiorna due esempi che ho trovato nei miei preferiti:

Qualche svantaggio nell'usare il riferimento const durante l'iterazione sui tipi di base?
Posso iterare facilmente sui valori di una mappa utilizzando un ciclo for basato su intervallo?

Per favore, concentrati sulla domanda: perché dovrei utilizzare auto && nei cicli for basati su intervallo?


4
Lo vedi davvero "spesso"?
Gare di leggerezza in orbita

2
Non sono sicuro che ci sia abbastanza contesto nella tua domanda per capire quanto sia "folle" dove lo vedi.
Gare di leggerezza in orbita

2
@LightnessRacesinOrbit Per farla breve: perché dovrei usarlo auto&&in cicli for basati su intervallo?
Ali

Risposte:


94

L'unico vantaggio che posso vedere è quando l'iteratore di sequenza restituisce un riferimento proxy e devi operare su quel riferimento in modo non const. Ad esempio, considera:

#include <vector>

int main()
{
    std::vector<bool> v(10);
    for (auto& e : v)
        e = true;
}

Questo non viene compilato perché rvalue vector<bool>::referencerestituito da iteratornon si associa a un riferimento lvalue non const. Ma questo funzionerà:

#include <vector>

int main()
{
    std::vector<bool> v(10);
    for (auto&& e : v)
        e = true;
}

Detto questo, non codificherei in questo modo a meno che tu non sapessi di dover soddisfare un caso d'uso del genere. Cioè non lo farei gratuitamente perché fa sì che le persone si chiedano cosa stai facendo. E se lo facessi, non sarebbe male includere un commento sul perché:

#include <vector>

int main()
{
    std::vector<bool> v(10);
    // using auto&& so that I can handle the rvalue reference
    //   returned for the vector<bool> case
    for (auto&& e : v)
        e = true;
}

modificare

Quest'ultimo mio caso dovrebbe davvero essere un modello per avere un senso. Se sai che il ciclo gestisce sempre un riferimento proxy, allora autofunzionerebbe altrettanto bene auto&&. Ma quando il ciclo a volte gestiva riferimenti non proxy e talvolta riferimenti proxy, penso che auto&&sarebbe diventata la soluzione preferita.


3
D'altra parte, non c'è uno svantaggio esplicito , vero? (A parte le persone potenzialmente confuse, che non credo valga la pena menzionare, personalmente.)
ildjarn

7
Un altro termine per scrivere codice che confonde inutilmente le persone è: scrivere codice confuso. È meglio rendere il codice il più semplice possibile, ma non più semplice. Ciò contribuirà a mantenere il conto alla rovescia del bug. Detto questo, man mano che && diventa più familiare, forse tra 5 anni le persone si aspettano un idioma di auto && (supponendo che in realtà non faccia del male). Non so se accadrà o no. Ma la semplicità è negli occhi di chi guarda, e se scrivi per qualcosa di più che per te stesso, tieni in considerazione i tuoi lettori.
Howard Hinnant

7
Preferisco const auto&quando voglio che il compilatore mi aiuti a controllare di non modificare accidentalmente gli elementi nella sequenza.
Howard Hinnant

31
Personalmente mi piace utilizzare auto&&in codice generico dove ho bisogno di modificare gli elementi della sequenza. Se non lo faccio, mi limiterò a auto const&.
Xeo

7
@Xeo: +1 È grazie agli entusiasti come te, che sperimentano costantemente e spingono per modi migliori di fare le cose, che il C ++ continua ad evolversi. Grazie. :-)
Howard Hinnant

26

Utilizzando auto&&o riferimenti universali con una gamma basata for-loop ha il vantaggio che si coglie ciò che si ottiene. Per la maggior parte dei tipi di iteratori probabilmente otterrai un T&o un T const&per qualche tipo T. Il caso interessante è quello in cui la dereferenziazione di un iteratore produce un valore temporaneo: C ++ 2011 ha requisiti meno rigidi e gli iteratori non sono necessariamente tenuti a fornire un lvalue. L'uso di riferimenti universali corrisponde all'argomento forwarding in std::for_each():

template <typename InIt, typename F>
F std::for_each(InIt it, InIt end, F f) {
    for (; it != end; ++it) {
        f(*it); // <---------------------- here
    }
    return f;
}

L'oggetto funzione fpuò trattare T&, T const&e Tdifferentemente. Perché il corpo di un forloop basato sulla gamma dovrebbe essere diverso? Naturalmente, per trarre effettivamente vantaggio dall'aver dedotto il tipo utilizzando riferimenti universali, dovresti trasmetterli in modo corrispondente:

for (auto&& x: range) {
    f(std::forward<decltype(x)>(x));
}

Ovviamente, usare std::forward()significa accettare qualsiasi valore restituito da cui spostare. Non so (ancora?) Se oggetti come questo abbiano molto senso nel codice non modello. Posso immaginare che l'uso di riferimenti universali possa offrire più informazioni al compilatore per fare la cosa giusta. Nel codice basato su modelli rimane fuori dal prendere qualsiasi decisione su cosa dovrebbe accadere con gli oggetti.


9

Io praticamente uso sempre auto&&. Perché farti mordere da un caso limite quando non devi? È anche più breve da digitare e lo trovo semplicemente più ... trasparente. Quando lo usi auto&& x, allora sai che xè esattamente *it, ogni volta.


29
Il mio problema è che ti stai constarrendendo, auto&&se è const auto&sufficiente. La domanda chiede i casi d'angolo in cui posso essere morso. Quali sono i casi d'angolo che non sono stati ancora menzionati da Dietmar o Howard?
Ali

2
Ecco un modo per ottenere morso se si fa uso auto&&. Se il tipo che stai catturando deve essere spostato nel corpo del ciclo (ad esempio), e viene modificato in una data successiva in modo che si risolva in un const&tipo, il tuo codice continuerà a funzionare silenziosamente, ma le tue mosse saranno copie. Questo codice sarà molto ingannevole. Se, tuttavia, specifichi esplicitamente il tipo come riferimento al valore r, chiunque abbia cambiato il tipo di contenitore riceverà un errore di compilazione perché davvero volevi che questi oggetti fossero spostati e non copiati ...
cyberbisson

@cyberbisson Mi sono appena imbattuto in questo: come posso applicare un riferimento rvalue senza specificare esplicitamente il tipo? Qualcosa di simile for (std::decay_t<decltype(*v.begin())>&& e: v)? Immagino che ci sia un modo migliore ...
Jerry Ma

@JerryMa Dipende da quello che ne sai del tipo. Come accennato in precedenza, auto&&fornirà un "riferimento universale", quindi qualsiasi altra cosa diversa da questo ti darà un tipo più specifico. Se vsi presume che sia a vector, potresti farlo decltype(v)::value_type&&, che è quello che presumo tu volessi prendendo il risultato di operator*su un tipo iteratore. Potresti anche decltype(begin(v))::value_type&&controllare i tipi dell'iteratore invece del contenitore. Se abbiamo così poche informazioni sul tipo, però, potremmo considerare un po 'più chiaro semplicemente andare con auto&&...
cyberbisson
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.