Nein, man lernt tatsächlich nie aus. Seit C++11 können wir ja schreiben

vector data{};
...
for(auto e : data) { cout << e; };

Das ist schon ganz schön praktisch gegenüber for(vector it=data.begin();...) { cout << *it; }, hat aber den großen Nachteil, dass erstens e hier oimmer kopiert wird und zweitens Änderungen an e sich nicht in data auswirken.

Stimmt, und deshalb erzähle ich auch immer, dass man daran danken sollte, in solchen Schleifen zuerst immer zu prüfen, ob man stattdessen nicht auto& oder const auto& verwenden sollte:

vector<int> data{};
...
for(auto& e : data) { e += 1; };

Das verhindert Kopie und data wird auch wirklich modifiziert.

Jetzt werde ich gerade erinnert, dass das nicht immer reicht. Zum Beispiel kann es keine (echte) Referenz in einen vector<bool> geben, weil man einzelne Bits nicht adressieren (also referenzieren) kann. Das gäbe mit auto& einen Fehler.

Daher, jetzt meine neue Regel: Man probiere es mit auto&&. Das ist dann der Einsatz der Meyerschen Universellen Referenzen, soweit ich das erkennen kann:

vector<int> data{};
...
for(auto&& e : data) { e += 1; };

Im Effekt bekommt man eine &, wenn nötig und eine &&, wenn erlaubt. Die genaue Erklärung was passiert, folgt in einem Detailbeitrag. Bis dahin probiere ich diese neue Merkregel mal aus.

Nachtrag

In einem Vorschlagsdokument des C++-Standards steht das Dilemma beschrieben:

What should we do about this problem? We can teach users to write
for (auto& elem : range) or for (const auto& elem : range), but they’re
imperfect (for proxies or mutation, respectively), and they require novices to
be introduced to auto and references earlier than otherwise necessary.
As explained immediately below, for (auto&& elem : range) is superior, but
it’s terrible to teach to novices. Even experienced C++98/03 programmers can
find it difficult to learn how rvalue references, the template argument
deduction tweak, and reference collapsing interact to produce what Scott Meyers
calls „universal references“ (see [1]), and auto&& is far less commonly seen
than T&& in perfect forwarding.

Andererseits sagt Herb Sutter in Back To Basics, dass man auto&& nicht für lokale Variablen verwenden soll. Da stimme ich im Prinzip auch zu, aber grüble, ob sein Gesagtes nicht bei diesen auto-for-Schleifen eine Ausnahme sind. Hauptsächlich im Bezug der Einfachheit allerdings.

„STL“ gibt etwas mehr Erklärung hier und bezieht auch den kommenden C++-Standard (wahrscheinlich) mit ein, der…

Ausblick

…eine neue Syntax enthält, die genau diese Schreibweise abkürzt: Einfach den gesamten Typen in der Schleifendeklaration weglassen und der Compiler macht daraus automatisch auto&&:

vector<int> data{};
...
for(e : data) { e += 1; };

…in der (fernen) Zukunft (vielleicht).

Zusammenfassung

Mit all den Informationen, die ich nun habe, möchte es noch einmal zusammenfassen. Vor allem „STL“ (oben) und Howard Hinnant konnten mir Dinge klären

Ab C++17 gilt: Verwende for(e :...) wann immer möglich.

vector<int> data{};
...
for(e : data) { e += 1; };

Bis dahin, mit C++11 alleine, gilt:

In 99.9% der Fälle und somit für alle Entwickler bis zum braunen Gürtel gilt (immernoch): Verwende for(auto &x: ...) oder for(const auto &x: ...) wenn Du kannst und kopiere nur wenn nötig:

vector<int> data{};
...
for(auto &e : data) { e += 1; };
...
for(const auto &e : data) { cout << e; };

Entwickler mit dem schwarzen Gürtel, und/oder innerhalb einer Bibliothek mit einem Funktionstemplate für den Typ von data, kann es sinnvoll sein for(auto &&x: ...) zu verwenden:

vector<int> data{};
template 
void func(T&& data) {
  ...
  for(auto &&e : data) { e += 1; };
  ...
}

Denn ob RValue-Ref oder LValue-Ref ist nur dann relevant, wenn data für seine Iteratoren (temporäre) Proxy-Objekte benötigt (wie vector<bool>). Selbst wenn man dieses Konstrukt außerhalb eines Funktionstemplate verwendet, wird man && nicht brauchen, sondern kann sich bewusst zwischen [const] auto& und [const] auto entscheiden.