Betrachten Sie einen Standard für die Schleife:
for (int i = 0; i < 10; ++i)
{
// do something with i
}
Ich möchte verhindern, dass die Variable i
im Hauptteil der for
Schleife geändert wird.
Ich kann jedoch nicht deklarieren i
, const
da dies die Inkrement-Anweisung ungültig macht. Gibt es eine Möglichkeit, i
eine const
Variable außerhalb der Inkrement-Anweisung zu erstellen?
const int i
Argument. Die Veränderbarkeit des Index wird nur dort angezeigt, wo er benötigt wird, und Sie können dasinline
Schlüsselwort verwenden, damit er keine Auswirkungen auf die kompilierte Ausgabe hat.const
, mit denen wir anfangen möchten.Antworten:
Ab c ++ 20 können Sie Bereiche :: Ansichten :: iota wie folgt verwenden :
for (int const i : std::views::iota(0, 10)) { std::cout << i << " "; // ok i = 42; // error }
Hier ist eine Demo .
Ab c ++ 11 können Sie auch die folgende Technik verwenden, die eine IIILE (sofort aufgerufener Inline-Lambda-Ausdruck) verwendet:
int x = 0; for (int i = 0; i < 10; ++i) [&,i] { std::cout << i << " "; // ok, i is readable i = 42; // error, i is captured by non-mutable copy x++; // ok, x is captured by mutable reference }(); // IIILE
Hier ist eine Demo .
Beachten Sie,
[&,i]
dass diesi
durch eine nicht veränderbare Kopie erfasst wird und alles andere durch eine veränderbare Referenz erfasst wird. Das();
am Ende der Schleife bedeutet einfach, dass das Lambda sofort aufgerufen wird.quelle
&
Erfassung beschweren , was die explizite Erfassung jeder Referenz erzwingen würde - was dies recht macht schwerfällig. Ich vermute auch, dass dies zu einfachen Fehlern führen kann, bei denen ein Autor das vergisst()
und der Code niemals aufgerufen wird. Dies ist leicht klein genug, um auch bei der Codeüberprüfung übersehen zu werden.[&]
da diese mit Codierungsstandards wie AUTOSAR (Regel A5-1-2), HIC ++ und meiner Meinung nach auch MISRA (nicht sicher) in Konflikt stehen. Es ist nicht so, dass es nicht richtig ist; Es ist so, dass Organisationen diese Art von Code verbieten, um den Standards zu entsprechen. Wie für die()
, die neueste nicht gcc Version Flagge nicht einmal mit-Wextra
. Ich denke immer noch, dass der Ansatz ordentlich ist; Es funktioniert einfach nicht für viele Organisationen.Für alle, die Cigiens
std::views::iota
Antwort mögen, aber nicht in C ++ 20 oder höher arbeiten, ist es ziemlich einfach, eine vereinfachte und leichtgewichtige Version vonstd::views::iota
kompatibel zu implementierenc ++ 11 oder höher.Alles was es erfordert ist:
operator++
undoperator*
), der einen ganzzahligen Wert umschließt (z. B. einint
)begin()
undend()
zurückgibt. Dadurch kann es in bereichsbasiertenfor
Schleifen arbeitenEine vereinfachte Version davon könnte sein:
#include <iterator> // This is just a class that wraps an 'int' in an iterator abstraction // Comparisons compare the underlying value, and 'operator++' just // increments the underlying int class counting_iterator { public: // basic iterator boilerplate using iterator_category = std::input_iterator_tag; using value_type = int; using reference = int; using pointer = int*; using difference_type = std::ptrdiff_t; // Constructor / assignment constexpr explicit counting_iterator(int x) : m_value{x}{} constexpr counting_iterator(const counting_iterator&) = default; constexpr counting_iterator& operator=(const counting_iterator&) = default; // "Dereference" (just returns the underlying value) constexpr reference operator*() const { return m_value; } constexpr pointer operator->() const { return &m_value; } // Advancing iterator (just increments the value) constexpr counting_iterator& operator++() { m_value++; return (*this); } constexpr counting_iterator operator++(int) { const auto copy = (*this); ++(*this); return copy; } // Comparison constexpr bool operator==(const counting_iterator& other) const noexcept { return m_value == other.m_value; } constexpr bool operator!=(const counting_iterator& other) const noexcept { return m_value != other.m_value; } private: int m_value; }; // Just a holder type that defines 'begin' and 'end' for // range-based iteration. This holds the first and last element // (start and end of the range) // The begin iterator is made from the first value, and the // end iterator is made from the second value. struct iota_range { int first; int last; constexpr counting_iterator begin() const { return counting_iterator{first}; } constexpr counting_iterator end() const { return counting_iterator{last}; } }; // A simple helper function to return the range // This function isn't strictly necessary, you could just construct // the 'iota_range' directly constexpr iota_range iota(int first, int last) { return iota_range{first, last}; }
Ich habe oben definiert,
constexpr
wo es unterstützt wird, aber für frühere Versionen von C ++ wie C ++ 11/14 müssen Sie möglicherweise entfernen,constexpr
wo es in diesen Versionen nicht legal ist, um dies zu tun.Mit dem obigen Boilerplate kann der folgende Code in Pre-C ++ 20 verwendet werden:
for (int const i : iota(0, 10)) { std::cout << i << " "; // ok i = 42; // error }
Die den Willen erzeugen gleiche Anordnung wie die C ++ 20 -
std::views::iota
Lösung und der klassischenfor
-loop Lösung , wenn optimiert.Dies funktioniert mit allen C ++ 11-kompatiblen Compilern (z. B. Compilern wie
gcc-4.9.4
) und erzeugt immer noch eine nahezu identische Assembly wie ein Basic-for
Loop-Gegenstück.Hinweis: Die
iota
Hilfsfunktion dient nur der Feature-Parität mit der C ++ 20-std::views::iota
Lösung. aber realistisch könnte man auch direkt ein konstruiereniota_range{...}
anstatt aufzurufeniota(...)
. Ersteres bietet nur einen einfachen Upgrade-Pfad, wenn ein Benutzer in Zukunft zu C ++ 20 wechseln möchte.quelle
int
und das Erstellen einer "Range" -Klasse, um den Anfang / das Ende zurückzugebenDie KISS Version ...
for (int _i = 0; _i < 10; ++_i) { const int i = _i; // use i here }
Wenn Ihr Anwendungsfall nur darin besteht, eine versehentliche Änderung des Schleifenindex zu verhindern, sollte dies einen solchen Fehler offensichtlich machen. (Wenn Sie absichtliche Änderungen verhindern möchten , viel Glück ...)
quelle
_
. Und ein bisschen Erklärung (zB Umfang) wäre hilfreich. Ansonsten ja, schön KISSy.i_
wäre konformer._i
in der Schleife noch geändert werden.std::views::iota
um einen vollständig kugelsicheren Weg zu finden. Der Text der Antwort erklärt seine Grenzen und wie sie versucht, die Frage zu beantworten. Eine Reihe von überkompliziertem C ++ 11 macht die Heilung in Bezug auf einfach zu lesende, leicht zu wartende IMO schlechter als die Krankheit. Dies ist für alle, die C ++ kennen, immer noch sehr einfach zu lesen und erscheint als Redewendung vernünftig. (Sollte aber vermeiden, führende Unterstrichnamen zu verwenden.)_Uppercase
unddouble__underscore
Bezeichner sind reserviert._lowercase
Bezeichner sind nur im globalen Bereich reserviert.Wenn Sie keinen Zugriff auf haben c ++ 20, typische Überarbeitung mit einer Funktion
#include <vector> #include <numeric> // std::iota std::vector<int> makeRange(const int start, const int end) noexcept { std::vector<int> vecRange(end - start); std::iota(vecRange.begin(), vecRange.end(), start); return vecRange; }
jetzt könntest du
for (const int i : makeRange(0, 10)) { std::cout << i << " "; // ok //i = 100; // error }
( Siehe eine Demo )
Update : Inspiriert von dem Kommentar von @ Human-Compiler habe ich mich gefragt, ob die gegebenen Antworten einen Unterschied in Bezug auf die Leistung haben. Es stellt sich heraus, dass mit Ausnahme dieses Ansatzes alle anderen Ansätze überraschenderweise die gleiche Leistung aufweisen (für den Bereich
[0, 10)
). Derstd::vector
Ansatz ist der schlechteste.( Siehe Online-Schnellbank )
quelle
vector
. Wenn der Bereich sehr groß ist, kann dies schlecht sein.std::vector
ist relativ gesehen ziemlich schrecklich, wenn der Bereich ebenfalls klein ist, und könnte sehr schlecht sein, wenn dies eine kleine innere Schleife sein sollte, die viele, viele Male lief. Einige Compiler (wie das Klirren mit libc ++, aber nicht libstdc ++) können das Neu- / Löschen einer Zuordnung optimieren, die der Funktion nicht entgeht. Andernfalls kann dies leicht der Unterschied zwischen einer kleinen, vollständig abgewickelten Schleife und einem Aufruf vonnew
+ seindelete
und vielleicht tatsächlich in dieser Erinnerung speichern.const i
ist in den meisten Fällen einfach nicht den Aufwand wert, ohne C ++ 20-Möglichkeiten, die es billig machen. Insbesondere bei Bereichen mit Laufzeitvariablen, die es für den Compiler weniger wahrscheinlich machen, alles weg zu optimieren.Könnten Sie nicht einfach einen Teil oder den gesamten Inhalt Ihrer for-Schleife in eine Funktion verschieben, die i als const akzeptiert?
Es ist weniger optimal als einige vorgeschlagene Lösungen, aber wenn möglich, ist dies recht einfach.
Edit: Nur ein Beispiel, da ich eher unklar bin.
for (int i = 0; i < 10; ++i) { looper( i ); } void looper ( const int v ) { // do your thing here }
quelle
Und hier ist eine C ++ 11-Version:
for (int const i : {0,1,2,3,4,5,6,7,8,9,10}) { std::cout << i << " "; // i = 42; // error }
Hier ist eine Live-Demo
quelle
{..}
. Sie müssen etwas hinzufügen, um diese Funktion zu aktivieren. Zum Beispiel wird Ihr Code beschädigt , wenn Sie keine richtigen Header hinzufügen: godbolt.org/z/esbhra . Es<iostream>
ist eine schlechte Idee, für andere Header weiterzuleiten !#include <cstdio> #define protect(var) \ auto &var ## _ref = var; \ const auto &var = var ## _ref int main() { for (int i = 0; i < 10; ++i) { { protect(i); // do something with i // printf("%d\n", i); i = 42; // error!! remove this and it compiles. } } }
Hinweis: Wir müssen den Gültigkeitsbereich aufgrund einer erstaunlichen Dummheit in der Sprache verschachteln: Die im
for(...)
Header deklarierte Variable befindet sich auf derselben Verschachtelungsebene wie die in der{...}
zusammengesetzten Anweisung deklarierten Variablen . Dies bedeutet zum Beispiel:for (int i = ...) { int i = 42; // error: i redeclared in same scope }
Was? Haben wir nicht einfach eine geschweifte Klammer geöffnet? Darüber hinaus ist es inkonsistent:
void fun(int i) { int i = 42; // OK }
quelle
Ein einfacher Ansatz, der hier noch nicht erwähnt wurde und in jeder Version von C ++ funktioniert, besteht darin, einen funktionalen Wrapper um einen Bereich zu erstellen, ähnlich wie
std::for_each
dies bei Iteratoren der Fall ist. Der Benutzer ist dann dafür verantwortlich, ein Funktionsargument als Rückruf zu übergeben, der bei jeder Iteration aufgerufen wird.Zum Beispiel:
// A struct that holds the start and end value of the range struct numeric_range { int start; int end; // A simple function that wraps the 'for loop' and calls the function back template <typename Fn> void for_each(const Fn& fn) const { for (auto i = start; i < end; ++i) { const auto& const_i = i; fn(const_i); } } };
Wo die Verwendung wäre:
numeric_range{0, 10}.for_each([](const auto& i){ std::cout << i << " "; // ok //i = 100; // error });
Alles, was älter als C ++ 11 ist, würde stecken bleiben und einen stark benannten Funktionszeiger an
for_each
(ähnlich wiestd::for_each
) übergeben, aber es funktioniert immer noch.Hier ist eine Demo
Obwohl dies für
for
Schleifen in C ++ möglicherweise nicht idiomatisch ist , ist dieser Ansatz in anderen Sprachen weit verbreitet. Funktionale Wrapper sind aufgrund ihrer Zusammensetzbarkeit in komplexen Aussagen sehr elegant und können sehr ergonomisch verwendet werden.Dieser Code ist auch einfach zu schreiben, zu verstehen und zu warten.
quelle
[&]
oder[=]
) verbieten , um bestimmte Sicherheitsstandards einzuhalten. Dies kann dazu führen, dass das Lambda aufgebläht wird und jedes Mitglied manuell erfasst werden muss. Nicht alle Organisationen tun dies, daher erwähne ich dies nur als Kommentar und nicht in der Antwort.template<class T = int, class F> void while_less(T n, F f, T start = 0){ for(; start < n; ++start) f(start); } int main() { int s = 0; while_less(10, [&](auto i){ s += i; }); assert(s == 45); }
vielleicht nenn es
for_i
Kein Overhead https://godbolt.org/z/e7asGj
quelle