C++ kapiere 'range based for loops' nicht

lemon03

Lieutenant
Registriert
Juli 2004
Beiträge
550
Hallo,

warum ist das falsch?

Code:
for (int i : 100)

Bekomme die Meldung

Code:
error: 'begin' was not declared in this scope|
error: 'end' was not declared in this scope|

Danke und LG
 
Weil das in C++ nicht funktioniert?

Klassisch wäre

Code:
for( int i = 0; i < 100; i++ )
{
   // Do stuff.
}

In neueren C++ Versionen gibt's foreach Schleifen, die gehen denke ich auch über int arrays bspw., wenn die on the fly erstellt werden können müsste das so ähnlich möglich sein wie du es willst.
Da kenne ich mich aber im aktuellen C++ nicht weiter aus und du musst selbst nachschauen.

Python kann das was du willst bspw. ganz einfach mit
Code:
for i in range(0,100):
    #do stuff
 
Dann deklariere doch mal ein Anfang und ein Ende?
 
Diese for-loops wurden ab dem C++11 standard eingeführt, und ich wollte sie eben benutzen um das klassische 'von 0 bis 100' zu ersetzen. Selbst habe ich sie schon eingesetzt, zB
Code:
for (auto j : char_col)
    {
        for (auto i : char_form)
        {
            ch.Char.UnicodeChar = i;
            ch.Attributes = j;
            palette_chars.push_back(ch);
        }
    }
(Sorry für das jetzt nicht nachvollziehbare Beispiel, kapiere nur eben nicht, warum analog mein Eingangsbeispiel nicht funktioniert)
In der Referenz stehen folgende Beispiele:
Code:
int main() {
    std::vector<int> v = {0, 1, 2, 3, 4, 5};
 
    for (const int& i : v) // access by const reference
        std::cout << i << ' ';
    std::cout << '\n';
 
    for (auto i : v) // access by value, the type of i is int
        std::cout << i << ' ';
    std::cout << '\n';
 
    for (auto&& i : v) // access by reference, the type of i is int&
        std::cout << i << ' ';
    std::cout << '\n';
 
    for (int n : {0, 1, 2, 3, 4, 5}) // the initializer may be a braced-init-list
        std::cout << n << ' ';
    std::cout << '\n';
 
    int a[] = {0, 1, 2, 3, 4, 5};
    for (int n : a) // the initializer may be an array
        std::cout << n << ' ';
    std::cout << '\n';
 
    for (int n : a)  
        std::cout << 1 << ' '; // the loop variable need not be used
    std::cout << '\n';
 
}
Wenn man jetzt statt den vector einfach einen int=100 nimmt, kapiere ich nicht, warum mein Beispiel nicht funktioniert?

@ Yuuri

Und das kapiere ich eben nicht.
 
Alle diese Beispiele beziehen sich auf Arrays bzw. eine Abwandlung davon.
Du willst aber eine Zählschleife. 100 ist nunmal kein array.
Dein Denkfehler ist, dass du eine Funktionalität nutzt die du falsch verstehst. Das würde funktionieren wenn 100 eine array mit 0;1;2... usw wäre. Ist es aber nicht. Also kannst du dir da keine einzelnen Werte rauspicken wie 1, 2 usw.
 
Mmh, ich glaube, so dämmerts mir. Würde ich also statt der 100 ein int array/vector mit hundert Einträgen habe, würde es so gehen?

EDIT: stimmt, so geht es dann
Code:
std::array <int, 4> arr = { 0, 1, 2, 3 }; //bitte bis 100 ausfüllen...
for (int i : arr) std::cout << i << '\n';
EDIT2: aber so stehts ja schon oben :rolleyes:


Gibt es dann noch eine Möglichkeit, Ungetüme wie 'for (int i = 0; i < 100; ++ i)' kürzer zu fassen?
 
Zuletzt bearbeitet:
War nur sone Idee, 'for (int i : 100)' erschien mir auf den ersten Blick so schön einfach. Gibts ja so Leute, die zB für eine Endlosschleife
Code:
for (;;)
schreiben, dachte mir kann jemand einen Tipp geben, wie man das für einfache Zählschleifen schreibt.
Ergänzung ()

@ vander

dann wüsste ich gerne, warum in einigen Foren
Code:
while (true)
verpönt ist, bzw warum man sich darüber lustig macht? Ist ja wohl lesbarer als das genannte
Code:
for (;;)
?
 
Zuletzt bearbeitet: (code tags wg smiley umwandlung)
Du könntest Dir eine eigene Klasse bauen, die entsprechende Iterators anbietet... ungefähr so:
Code:
#include <algorithm>
#include <iostream>

namespace util {
  class range {
    public:
    class iterator {
      friend class range;
      public:
      long int operator *() const { return i_; }
      const iterator &operator ++() { ++i_; return *this; }
      iterator operator ++(int) { iterator copy(*this); ++i_; return copy; }

      bool operator ==(const iterator &other) const { return i_ == other.i_; }
      bool operator !=(const iterator &other) const { return i_ != other.i_; }

      protected:
      iterator(long int start) : i_ (start) { }

      private:
      unsigned long i_;
    };

    iterator begin() const { return begin_; }
    iterator end() const { return end_; }
    range(long int  begin, long int end) : begin_(begin), end_(end) {}
    private:
    iterator begin_;
    iterator end_;
  };
}


int main(int argc, char* argv[]) {
  auto range = util::range(0, 100);
  for(auto i : range) {
    std::cout << i << std::endl;
  }
  return 0;
}
 
Überleg mal: Was sind denn die Werte, welche von i durchlaufen werden bei for (int i : 100)? 0 bis 100? Oder 1 bis 100?
Das ist nicht eindeutig, weil 100 quasi nur das Ende aber nicht den Start eindeutig vorgibt.
 
Kanibal schrieb:
Du könntest Dir eine eigene Klasse bauen, die entsprechende Iterators anbietet...

Was zur Hölle?

Hast du dir mal den resultierenden Disassembly-Code angeschaut?

Hier merkt man sehr schön, wieviel unnütze CPU-Instruktionen verschwendet werden - nur um ne Schleifenoperation zu Abstrahieren!

Ganz zu schweigen das die API falsch ist, "end" sollte auch wirklich end sein und nicht end - 1 -.-

Normale for-loop (x64, Debug):
Code:
	for (int i = firstIndex; i <= lastIndex; ++i) {
000000013F4424DE  mov         eax,dword ptr [firstIndex]  
000000013F4424E1  mov         dword ptr [rbp+10C4h],eax  
000000013F4424E7  jmp         main+0F7h (013F4424F7h)  
000000013F4424E9  mov         eax,dword ptr [rbp+10C4h]  
000000013F4424EF  inc         eax  
000000013F4424F1  mov         dword ptr [rbp+10C4h],eax  
000000013F4424F7  mov         eax,dword ptr [lastIndex]  
000000013F4424FA  cmp         dword ptr [rbp+10C4h],eax  
000000013F442500  jg          main+127h (013F442527h)  
		std::cout << i << std::endl;
000000013F442502  mov         edx,dword ptr [rbp+10C4h]  
000000013F442508  mov         rcx,qword ptr [__imp_std::cout (013F451150h)]  
000000013F44250F  call        qword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (013F451170h)]  
000000013F442515  lea         rdx,[std::endl<char,std::char_traits<char> > (013F4410B9h)]  
000000013F44251C  mov         rcx,rax  
000000013F44251F  call        qword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (013F451178h)]  
	}

Auto range Ding (x64, Debug):
Code:
	auto range = util::range(firstIndex, lastIndex + 1);
000000013F96282E  mov         eax,dword ptr [lastIndex]  
000000013F962831  inc         eax  
000000013F962833  mov         r8d,eax  
000000013F962836  mov         edx,dword ptr [firstIndex]  
000000013F962839  lea         rcx,[range]  
000000013F962840  call        util::range::range (013F961235h)  
	for (auto i : range) {
000000013F962845  lea         rax,[range]  
000000013F96284C  mov         qword ptr [rbp+10E8h],rax  
000000013F962853  lea         rdx,[rbp+1104h]  
000000013F96285A  mov         rcx,qword ptr [rbp+10E8h]  
000000013F962861  call        util::range::begin (013F9612BCh)  
000000013F962866  lea         rdx,[rbp+1124h]  
000000013F96286D  mov         rcx,qword ptr [rbp+10E8h]  
000000013F962874  call        util::range::end (013F9611C2h)  
000000013F962879  jmp         main+137h (013F962887h)  
000000013F96287B  lea         rcx,[rbp+1104h]  
000000013F962882  call        util::range::iterator::operator++ (013F961415h)  
000000013F962887  lea         rdx,[rbp+1124h]  
000000013F96288E  lea         rcx,[rbp+1104h]  
000000013F962895  call        util::range::iterator::operator!= (013F9613A2h)  
000000013F96289A  movzx       eax,al  
000000013F96289D  test        eax,eax  
000000013F96289F  je          main+188h (013F9628D8h)  
000000013F9628A1  lea         rcx,[rbp+1104h]  
000000013F9628A8  call        util::range::iterator::operator* (013F961271h)  
000000013F9628AD  mov         dword ptr [rbp+1144h],eax  
		std::cout << i << std::endl;
000000013F9628B3  mov         edx,dword ptr [rbp+1144h]  
000000013F9628B9  mov         rcx,qword ptr [__imp_std::cout (013F971150h)]  
000000013F9628C0  call        qword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (013F971170h)]  
000000013F9628C6  lea         rdx,[std::endl<char,std::char_traits<char> > (013F9610B9h)]  
000000013F9628CD  mov         rcx,rax  
000000013F9628D0  call        qword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (013F971178h)]  
	}
000000013F9628D6  jmp         main+12Bh (013F96287Bh)

Der Code:
Code:
#include <stdio.h>

#include <algorithm>
#include <iostream>
#include <stdint.h>

namespace util {
	class range {
	public:
		class iterator {
			friend class range;
		public:
			long int operator *() const {
				return i_;
			}
			const iterator &operator ++() {
				++i_; return *this;
			}
			iterator operator ++(int) {
				iterator copy(*this); ++i_; return copy;
			}

			bool operator ==(const iterator &other) const {
				return i_ == other.i_;
			}
			bool operator !=(const iterator &other) const {
				return i_ != other.i_;
			}

		protected:
			iterator(long int start) : i_(start) {
			}

		private:
			unsigned long i_;

		};

		iterator begin() const {
			return begin_;
		}
		iterator end() const {
			return end_;
		}
		range(long int  begin, long int end) : begin_(begin), end_(end) {
		}
	private:
		iterator begin_;
		iterator end_;

	};
};

int main(int argc, char *args[]) {

	int firstIndex = 0;
	int lastIndex = 100;

	// Make sure that we push useless stuff into the cacheline: Not sure if this will work...
	int arr[1024];
	for (int i = 0; i < 1024; ++i) {
		arr[i] = rand();
	}
	for (int i = 0; i < 1024; ++i) {
		arr[i] /= 2;
	}

	uint64_t startCycles = __rdtsc();
#if 1
	auto range = util::range(firstIndex, lastIndex + 1);
	for (auto i : range) {
		std::cout << i << std::endl;
	}
#else
	for (int i = firstIndex; i <= lastIndex; ++i) {
		std::cout << i << std::endl;
	}
#endif
	uint64_t endCycles = __rdtsc();
	uint64_t deltaCycles = endCycles - startCycles;
	std::cout << "Delta cycles: " << deltaCycles << std::endl;

	return 0;
}

Man kann auch mit nem Presslufthammer nen Nagel in die Wand schlagen.

Wenn man die CPU-Zyklen sich anschaut dann haben wir ne ungefähre Differenz von:

77752550 vs 86202203

Das ist ein Unterschied von 8449653 Zyklen!!!

Damit kann ich 281655 Wurzelberechnungen (30 Zyklen pro Squareroot) oder 2 Mio. Multiplikationen (4 Zyklen pro mul) ausführen.



Das ist Grund warum Software und das Web heutzutage einfach nur unglaublich langsam ist - wegen solch einem Abstraktionswahnsinn.
 
Zuletzt bearbeitet:
lemon03 schrieb:
dann wüsste ich gerne, warum in einigen Foren
Code:
while (true)
verpönt ist, bzw warum man sich darüber lustig macht? Ist ja wohl lesbarer als das genannte
Code:
for (;;)
?

als ich die frage gestellt habe wurde mir gesagt, das laege an der laenge der ausdruecke (ersteres hat ohne leerzeichen 11, zweiteres nur 7 zeichen). so grob unklar, was for(;; ) macht, ist das jetzt beim lesen auch nicht...
 
@Finalspace welcher Compiler? Mit oder ohne Optimierungen?

Aufgrund der ganzen unsinnigen Calls in deinem ASM-Code tippe ich mal auf ohne Optimierungen. Jeder halbwegs brauchbare Compiler sollte das aber problemlos wegoptimieren, bei Clang 4.0 kommt spätestens ab -O2 identischer Code bei beiden Varianten raus.

Ein Range-Iterator kann durchaus sinnvoll sein, wenn man ohnehin schon Funktionen herumfliegen hat, die Iteratoren akzeptieren (der Iterator-Typ ist dann eben ein Template-Parameter jener Funktionen). Solange da keine virtuellen Funktionen aufgerufen werden, wie etwa bei Java, ist das eine Zero Cost-Abstraktion.

Gleiches gilt nebenbei auch für Dinge wie Range-Checks bei Array-Zugriffen. Solange der Compiler irgendwie wissen kann, dass ein Index kleiner ist als die Länge des Arrays, wird auch der Range-Check wegoptimiert.

Yuuri schrieb:
Code:
for (auto&& [first,second] : mymap) [...]
Wusste gar nicht, dass C++ auch eine Syntax hat, um Werte-Paare zu zerlegen, wieder was gelernt.
 
Zuletzt bearbeitet:
@Finalspace
Du müsstest noch Compilerversion und Flags angeben, um mit den "Benchmarks" irgendeine Aussagekraft generieren zu können.
Nur so viel: Schau Dir mal das Disassembly von 'clang4.0.0' mit '-O3 -std=c++14' an.

Grundsätzlich stimme ich Dir aber zu: es macht oftmals wenig Sinn, eine klassische for-Schleife durch Iterators zu ersetzen.
 
@Finalspace: Selten so einen sinnlosen Benchmark gesehen.

Man kanns übrigens auch noch etwas minimalistischer gestalten:
Code:
namespace detail {
struct IIterator {
	int _v;
	int operator*() const {
		return _v;
	}
	void operator++() {
		++_v;
	}
	friend bool operator!=(IIterator l, IIterator r) {
		return l._v != r._v;
	}
};

struct Irange_t {
	int _begin;
	int _end;

	IIterator begin() const { return { _begint };	}
	IIterator end()   const { return { _end };	}
};
}

detail::Irange_t irange(int begin, int end) {
	return { begin, end };
}
detail::Irange_t irange(int end) {
	return { 0, end };
}

int main() {
	for (auto i : irange(5, 20)) {
		std::cout << i << std::endl;
	}
	for (auto i : irange(20)) {
		std::cout << i << std::endl;
	}
}
Das ganze kann man noch mit templates für verschiedene Integertypen generalisieren, aber ich wollte es einfach halten.
Viel weniger Tipparbeit ist
Code:
for (auto i : irange(20))
dann allerdings nicht nicht wirklich
 
Ist mit MSVC++ kompiliert, unter Debug-Mode:

/GS /W3 /Zc:wchar_t /ZI /Gm /Od /Fd"x64\Debug\vc140.pdb" /Zc:inline /fp:precise /D "_DEBUG" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /errorReport:prompt /WX- /Zc:forScope /RTC1 /Gd /MDd /Fa"x64\Debug" /EHsc /nologo /Fo"x64\Debug" /Fp"x64\Debug\GrottigerCode.pch"

Ja mit Optimierungen ist die Range Loop schneller, aber immer noch langsamer als die normale Forschleife unter VC++! Selbst wenn CLANG das komplett herausoptimiert, dann ist das für mich dennoch untragbar - da man 90% der Zeit ohne Optimierungen entwickelt!
Ich kenn niemand der komplett immer Optimierungen (O2) an hat und dann problemlos alles Debuggen kann. Alleine schon das Code verschobene werden kann, macht es deutlich schwerer als unter Debug zu arbeiten. Das macht man eigentlich nur, wenn es beim Kunden als Release läuft und man nen Problem hat was nur unter Release auftritt.

Ich kann beim besten Willen nicht verstehen wie man sowas gegenüber normalen for-schleifen bevorzugen kann. Einfach nur Tippfaul und angst vorm programmieren -.-

Automatische Iteratoren sollen verwendet werden wenn es Sinn macht, z.b. wenn man die Anzahl nicht weiß (DB-Query). Von mir aus noch in STL Containern, wie z.b. std::map - aber bitte nicht bei normalen Aufzählungen.

Ich bin raus, das ist mir echt zuviel...
 
Zuletzt bearbeitet:
Kanibal schrieb:
Besser ist es. Du beschwerst Dich gerade über die 'Performance' eines Debug-Builds.

Ey, Moment mal. Er programmiert seit 1995. 1995! Du hast Unrecht. :(
 
lemon03 schrieb:
dann wüsste ich gerne, warum in einigen Foren
Code:
while (true)
verpönt ist, bzw warum man sich darüber lustig macht? Ist ja wohl lesbarer als das genannte
Code:
for (;;)
?

Einige Compiler werfen bei strengen Einstellungen eine Warning für "while(true)" raus (Conditional expression constant o.ä.), diese Warning gibts bei for(;; ) nicht. Ansonsten fällt mir kein wirklicher Grund ein. Mit solchen Details sollte man aber sowieso nicht allzuviel Zeit vergeuden... Mach es doch einfach so, wie es dir gefällt!

Auch interessant zu sehen, dass hier Assembly-Code von Debug-Builds verglichen wird!

:)
 
Zuletzt bearbeitet:
Zurück
Oben