C/C++: Kennt ihr diesen "Operator" schon?

Gast #6028902
Lesenswert?

Das folgende C++ Konstrukt hab ich grad so ähnlich im Netz entdeckt:
1
for (unsigned int i = stdv.size(); i --> 0; )
2
{
3
  std::cout << stdv[i];
4
}

Ich habs weder gekannt, noch auf anhieb verstanden. Da es auch in C 
geht, wollt ichs Euch nicht vorenthalten.

Ist von dort: 
https://stackoverflow.com/questions/275994/whats-the-best-way-to-do-a-backwards-loop-in-c-c-c
Gast #6028911
Lesenswert?

Standardkost würde ich jetzt nicht sagen.

Ich würde eher jeden verhauen, der solch ein Konstrukt tatsächlich 
benutzen möchte :)

Das fällt in die Rubrik kann man machen, sollte man aber nicht.
Gast #6028917
Lesenswert?

Hi,

wenn ich es richtig verstehe, dann wird die Variable i jetzt schon vor 
dem Vergleich dekrementiert, anstatt erst am Schleifenwendepunkt. Sollte 
man aber bedenken, wenn man i noch im Schleifenkörper braucht.

Ich persönlich halte von dieser Verkrypterei nicht viel, insbesondere C 
bringt das immer wieder in Verruf.

Viele Grüße,
Servo
Gast #6028926
Lesenswert?

> Standardkost würde ich jetzt nicht sagen.

Simple assignment, postfix decrement und relational operator gibts so 
seit mindestens 30 Jahren (ich vermute mal dass er auf diese 3 
Operatoren raus will). Kaum ein Programm kommt ohne die aus, 
0815-Standardkost. Wer die nicht (er)kennt, kennt nicht wirklich was.

Nix für ungut.
Gast #6028932
Lesenswert?

Das ursprüngliche Problem für diese rhetorische Frage war, ein Array 
rückwärts zu durchlaufen.

Wie durläuft man ein Array rückwärts, wenn man das Programmieren gelernt 
hat?, so dass man:

- unsgined int verwenden kann und
- mit den Arraydimensionen arbeiten kann, ohne +/- 1?
#6028959
Lesenswert?

C. P. P. Lovejoy schrieb:
> Carl D. schrieb:
>> Indem man von rbegin() nach rend() iteriert.
>
> Wie bekommt man dann den Index?

Vor dem Rückwärtsiterieren sollte man ansatzweise das Vorwärtsiterieren 
verstehen. Man braucht da keinen Index, der Iterator ist quasi ein 
Zeiger auf das vector/array/...-Element.

Z.B. int-Vector rückwärts ausgeben:
1
for ( auto i = v.rbegin(); i != v.rend(); i++ ) {
2
    std::cout << *i << std::endl;
3
}

C++11 vorausgesetzt.
Gast #6028971
Lesenswert?

leo schrieb:
>> wenn ich es richtig verstehe, dann wird die Variable i jetzt schon vor
>> dem Vergleich dekrementiert,
>
> Nein. Das ist ganz normales Postdekrement.

Ja hast recht. Insofern macht das noch weniger Sinn. Ich würde sowas 
auch niemals so hinschreiben, da stolpert man doch drüber. Ein einzelnes 
i--; oder i++; ist doch viel eindeutiger.

>
>> anstatt erst am Schleifenwendepunkt.
>
> Was soll das sein?

Ach komm. Ich weiß wirklich nicht, ob es da ein anderes Wort für gibt, 
aber in diesem Kontext kann ja man sich eindeutig denken was gemeint 
ist. Die for-Schleife ist vorprüfend, und das letzte Statement in der 
for-Klammer wird erst nach der Ausführung des Rumpfes abgearbeitet. An 
dieser Stelle "wendet" dann die Schleife. Daher meine kreative 
Wortschöpfung" :-)

Viele Grüße,
Servo
#6028975
Lesenswert?

leo schrieb:
> Servo schrieb:
>> wenn ich es richtig verstehe, dann wird die Variable i jetzt schon vor
>> dem Vergleich dekrementiert,
>
> Nein. Das ist ganz normales Postdekrement.

In der hier vorgestellten Syntax wird der Check (i>0) und der Dekrement 
(i--) vor dem Ausführen des Schleifenkörpers gemacht, wenn es
1
for (..; i > 0; i--)

wäre, dann würde der Dekrement nach dem Ausführen des Schleifenkörpers 
gemacht. Ersteres iteriert über [stdv.size() -1; 0], letzteres über 
[stdv.size(); 1].

Ich würde diese Form aber auch nicht nutzen. Da muss man drei mal 
schauen bis man kappiert was da passiert.
#6028978
Lesenswert?

leo schrieb:
> Carl D. schrieb:
>> Man braucht da keinen Index, der Iterator ist quasi ein
>> Zeiger auf das vector/array/...-Element.
>
> Was ist da der Unterschied zu einem Pointer-In(De-)krement? Mehr Syntax?
>
> leo

Z.B. daß v auch ein anderer Container mit Rückwärts-Iterator sein darf, 
ohne daß man einen (überladenen) [] Operator bräuchte.
Ein Iterator kann ein (Smart-)Pointer sein, oder aber auch was ganz 
anderes.
#6029003
Lesenswert?

C. P. P. Lovejoy schrieb:
> Carl D. schrieb:
>> C++11 vorausgesetzt.
>
> Danke fürs Beispiel. Schön, dass C schon immer eine Lösung hat, und C++
> im Zeitgeist frolockt.

In C++ kann v aber auch ein Objekt sein, das aus stdin liest und dessen 
Iterator über den * Operator das aktuelle Zeichen liefert, via ++ 
Operator das nächste Zeichen liest und dieses via != Operator mit '\n' 
vergleicht. Dann iteriert man mit dem gezeigten Code über die Zeichen 
einer Zeile. Da tut sich der Index schwer.
#6029135
Lesenswert?

Carl D. schrieb:
> C. P. P. Lovejoy schrieb:
>> Carl D. schrieb:
>>> C++11 vorausgesetzt.
>>
>> Danke fürs Beispiel. Schön, dass C schon immer eine Lösung hat, und C++
>> im Zeitgeist frolockt.
>
> In C++ kann v aber auch ein Objekt sein, das aus stdin liest und dessen
> Iterator über den * Operator das aktuelle Zeichen liefert, via ++
> Operator das nächste Zeichen liest und dieses via != Operator mit '\n'
> vergleicht. Dann iteriert man mit dem gezeigten Code über die Zeichen
> einer Zeile. Da tut sich der Index schwer.

Ein reverse-Iterator aber auch... :)
#6029221
Lesenswert?

Jeder Zeiger ist ein Iterator, aber nicht jeder Iterator ist ein Zeiger.

Ein Iterator ist ein abstraktes Konzept (nein, ich meine nicht concepts 
und requirements, denn das Iterator-Konzept ist Uralt in C++). Dieses 
Konzept verlangt eine bestimmte Operationenmenge. Jeder Dytentyp, der 
diese Typanforderungen erfüllt, ist eine Iterator. Und kann auch für die 
Algorithmen der stdlib verwendet werden.
Gast #6029228
Lesenswert?

übrigens haben Prozessoren mit einer Loop Einheit im Core meist das 
Problem den Schleifenzähler/Iterator nicht in der Load/Store Einheit 
gleichzeitig verwenden zu können.
Wenn man also den Iterator auch  als Array Index verwendet wird kann die 
"Zero Overhead Loop Unit" nicht verwendet werden  => Schleife benötigt 
mehr Takte.

Eine Zweite Variable die auch problemlos in die andere Richtung laufen 
kann ist effizienter.
Die HW-Loop Counter sind meist übrigens Down-Counter.
#6029246
Lesenswert?

C. P. P. Lovejoy schrieb:
> Das folgende C++ Konstrukt hab ich grad so ähnlich im Netz entdeckt:
>
>
1
> for (unsigned int i = stdv.size(); i --> 0; )
2
> {
3
>   std::cout << stdv[i];
4
> }
5
>
>
> Ich habs weder gekannt, noch auf anhieb verstanden. Da es auch in C
> geht, wollt ichs Euch nicht vorenthalten.
>
> Ist von dort:
> 
https://stackoverflow.com/questions/275994/whats-the-best-way-to-do-a-backwards-loop-in-c-c-c

Dort steht übrigens: "... typographically best ..."
Gast #6029252
Lesenswert?

Servo schrieb:
> wenn ich es richtig verstehe, dann wird die Variable i jetzt schon vor
> dem Vergleich dekrementiert, anstatt erst am Schleifenwendepunkt. Sollte
> man aber bedenken, wenn man i noch im Schleifenkörper braucht.

Wenn ich c richtig verstanden habe, dann wird der dritte Parameter im 
"Schleifenkopf"   am Ende  des "Schleifenkörpers" durchgeführt.

Zur Vollständigkeit :

Der erste Parameter im "Schleifenkopf" wird einmal  am Beginn  der 
Schleifeninitalisierung, der Zweite  vor Eintritt  im Schleifenkörper 
durchgeführt.
Gast #6029311
Lesenswert?

Ich glaube, da hat jemand ein "schönes" Konstrukt in die falsche 
Schleife gesteckt.

Standard-Anwendungsfall ist
1
   while(n-->0) ...

Wenn man n bekommt und es nicht als Index braucht. Z.b. n Mal einen Port 
toggeln soll.
#6029367
Lesenswert?

Bernd K. schrieb:
> Der Parser oder Tokenizer oder meinetwegen auch die ganze Grammatik ist
> krank wenn das erlaubt ist ohne einen Syntaxfehler auszuspucken.

Dabei ist es doch ganz einfach. In C sind Whitespaces und Zeilenumbrüche 
kein Syntaxelement. Sie können also weggelassen werden, solange sich der 
Ausdruck noch eindeutig parsen läßt.
Nur der Mensch braucht sie zur besseren Lesbarkeit. Also ist auch er in 
der Verantwortung, sie sinnvoll zu setzen.
Gast #6029399
Lesenswert?

C. P. P. Lovejoy schrieb:
> for (unsigned int i = stdv.size(); i --> 0; )

Ich verstehe nicht genau, was hier neu sein soll?
Habe nciht alles gelesen. Das sieht mir aber nach altem Hut aus. Kein 
neuer Operator.

i-->0 ist lediglich (i--)>0
Und das ist:
* Dekrementiere i
* liefere Vergleich, ob i (vor dem dekrement) größer als 0

Stichwort: Operator Precedence. Soltle man grob kennen von den Sprachen, 
die man verwendet.

und dann eben vor jedem Schleifendurchlauf durchgeführt (Zweites 
Statement in den Klammern der for-Schleife) und der Einsprung in den 
Schleifenkörper findet nur statt, wenn der Ausdruck != 0 liefert.

Ich stelle leider immer wieder fest, dass die Grundfesten von C, 
speziell eine for-Schleife, bei den meisten nicht richtig gelehrt 
wurden.

for (a; b; c) d;

1) Als erstes wird das Statement "a" ausgeführt.
2) Dann wird zur Prüfung der Einsprungbedingnung übergegangen. Dazu wird 
"b" ausgeführt. Der Rückgabewert von was auch immer da steht, 
entscheidet, ob der Schleifenkörper "d" ausgeführt wird.
3) Am Ende wird Statement "c" ausgeführt.
4) => Weiter bei Punkt 2)

Viele scheinen darauf festgenagelt zu sein, dass
1) a nur eine Initialisierung,
2) b nur eine Bedingung / Vergleich
3) und c nur ein Inkrement/Dekrement sein kann.

Da kann jedoch jedes beliebige Statement stehen.

Dazu ein kleines Beispiel:
Die Elemente einer verkettete Liste 'list_head' mit der Struktur
1
struct list_item {
2
 void *data;
3
 struct list_item *next;
4
}
können bspw. so gezählt werden (ja ich weiß, verkettete Listen sind 
langsam und skalieren schlecht):
1
int len;
2
struct list_item *iter;
3

4
for (len = 0, iter = list_head; iter; iter = iter->next, len++);
#6029563
Lesenswert?

M. H. schrieb:
> Viele scheinen darauf festgenagelt zu sein, dass
> 1) a nur eine Initialisierung,
> 2) b nur eine Bedingung / Vergleich
> 3) und c nur ein Inkrement/Dekrement sein kann.

M. H. schrieb:
> Da kann jedoch jedes beliebige Statement stehen.

Bei a) stimmt das so leider nicht.

Hier sind nur expression-statements und simple-declarations erlaubt.

M. H. schrieb:

> for (len = 0, iter = list_head; iter; iter = iter->next, len++);

Hier hast Du also für a) den Komma-Operator eingesetzt 
(expression-stmt).
Moderator #6029661
Lesenswert?

C. P. P. Lovejoy schrieb:
> C/C++: Kennt ihr diesen "Operator" schon?

Ein kurzes "Häh?" hat mit dieses --> schon entlockt :)

Aber auch wenn das korrekt ist, würde ich dennoch die Schreibweise

1
for (unsigned int i = stdv.size(); i-- > 0; )

bevorzugen, um anderen den "Häh?"-Effekt zu ersparen. Da ich unäre
Operatoren immer direkt (ohne Leerzeichen) an den Operanden hefte und
binäre Operatoren meist beidseitig mit Leerzeichen von den Operanden
trenne, ergibt sich diese Schreibweise bei mir ganz von selber.

Bernd K. schrieb:
> Der Parser oder Tokenizer oder meinetwegen auch die ganze Grammatik ist
> krank wenn das erlaubt ist ohne einen Syntaxfehler auszuspucken.

In den meisten Programmiersprachen können mehrere Operatoren direkt
hintereinander geschrieben werden, da sie neben ihrer eigentlichen
Funktion auch als Delimiter fungieren. So ist bspw.

1
a <-- b

in C, C++, Java, Python, Ruby, Pascal u.v.m. ein legaler Ausdruck, der
in C, C++ und Java äquivalent zu
1
a < (--b)

und in Python, Ruby und Pascal (wo es kein -- gibt) äquivalent zu

1
a < (-(-b))

ist.

So gesehen ist jede der genannten Sprachen "krank" ;-)

Eine der wenigen "gesunden" Ausnahmen stellt Haskell dar: Dort wird das
<-- tatsächlich als ein einzelner Operator geparst, der aber – da nicht
in der Standardbibliothek enthalten – selbst definiert werden muss. Soll
das Verhalten von Python, Ruby und Pascal nachgebildet werden, lautet
diese Definition ganz einfach

1
x <-- y   =   x < -(-y)

Dann ist 4 <-- 4 == False und 3 <-- 4 == True.
Gast #6029739
Lesenswert?

Yalu X. schrieb:
> Aber auch wenn das korrekt ist, würde ich dennoch die Schreibweise
>
> for (unsigned int i = stdv.size(); i-- > 0; )
>
> bevorzugen

Als Ausdruck künstlerischer Potenz vielleicht.
Wenn ich mit stdv.size-1 in die Schleife gehen will und auch die 0 haben 
will, dann sollte es auch so schreiben, falls ein Programmierer 
(Nachfolger) und nicht-Künstler das mal lesen muss. Also:
1
    /* langweilige version */
2
    for(unsigned int i = stdv.size()-1; i>=0; i--)
Ja, ist ein bisschen länger. Frag einfach mal 5 Leute: Die Funktion gibt 
10 zurück, mit welchen werten i wird die Schleife durchlaufen? und 
schaue, wo sie mehr straucheln.
#6029749
Lesenswert?

A. S. schrieb:
> Yalu X. schrieb:
>> Aber auch wenn das korrekt ist, würde ich dennoch die Schreibweise
>>
>> for (unsigned int i = stdv.size(); i-- > 0; )
>>
>> bevorzugen
>
> Als Ausdruck künstlerischer Potenz vielleicht.
> Wenn ich mit stdv.size-1 in die Schleife gehen will und auch die 0 haben
> will, dann sollte es auch so schreiben, falls ein Programmierer
> (Nachfolger) und nicht-Künstler das mal lesen muss. Also:
>
>
1
>     /* langweilige version */
2
>     for(unsigned int i = stdv.size()-1; i>=0; i--)
3
>
> Ja, ist ein bisschen länger. Frag einfach mal 5 Leute: Die Funktion gibt
> 10 zurück, mit welchen werten i wird die Schleife durchlaufen? und
> schaue, wo sie mehr straucheln.


Satire? :D

Falls nicht, hint:
1
.LBB0_1:
2
        jmp     .LBB0_1
#6029753
Lesenswert?

A. S. schrieb:
> Als Ausdruck künstlerischer Potenz vielleicht.
> Wenn ich mit stdv.size-1 in die Schleife gehen will und auch die 0 haben
> will, dann sollte es auch so schreiben, falls ein Programmierer
> (Nachfolger) und nicht-Künstler das mal lesen muss. Also:
>     /* langweilige version */
>     for(unsigned int i = stdv.size()-1; i>=0; i--)

Bamm!!! Juhu, der Klassiker!
Gast #6029854
Lesenswert?

Walter K. schrieb:
> Jau! Und Fahrrad nur mit Helm besteigen - und an die Strasse bei jeder
> 50cm hohen Böschung eine doppelte Leitplanke und nur noch in Warnweste
> und mit Schutzbrille rumrennen!!!

Du kannst das natürlich halten wie ein Dachdecker.

Einfache Tatsache ist aber, dass eine solche Schreibweise unüblich ist 
und der geneigte Leser erst mal drüber fällt, was da jetzt wirklich 
passiert.

Dazu gibts keine Perfomanceverbesserung gegenüber einer üblichen 
Schreibweise. Ergo -> Schrott der nur Fehler verursacht.
Gast #6030028
Lesenswert?

Peter D. schrieb:
> M. H. schrieb:
>> for (a; b; c) d;
>
> Zu beachten ist aber, daß mit continue zu c gesprungen wird.
> Bei do/while und while springt continue dagegen zum Vergleich.

Ist essentiell das gleiche bzw. sogar dasselbe Verhalten.
continue überspringt den Rest des Statementblocks "d". Bei einer 
for-Schleife kommt dann eben "c" vor "b".
#6030132
Lesenswert?

A. S. schrieb:
> Ja, ist ein bisschen länger. Frag einfach mal 5 Leute: Die Funktion gibt
> 10 zurück, mit welchen werten i wird die Schleife durchlaufen? und
> schaue, wo sie mehr straucheln.

Die Antwort, die man hier lernt wäre, dass man das nicht sagen kann, 
weil es implementation-defined ist. Und wenn im Body nicht irgendwas mit 
Seiteneffekt steht sogar UB.
Gast #6030145
Lesenswert?

Heiko L. schrieb:
> A. S. schrieb:
>> Ja, ist ein bisschen länger. Frag einfach mal 5 Leute: Die Funktion gibt
>> 10 zurück, mit welchen werten i wird die Schleife durchlaufen? und
>> schaue, wo sie mehr straucheln.
>
> Die Antwort, die man hier lernt wäre, dass man das nicht sagen kann,
> weil es implementation-defined ist. Und wenn im Body nicht irgendwas mit
> Seiteneffekt steht sogar UB.

Wo ist da etwas implementation-defined?
#6030147
Lesenswert?

mh schrieb:
> Heiko L. schrieb:
>> A. S. schrieb:
>>> Ja, ist ein bisschen länger. Frag einfach mal 5 Leute: Die Funktion gibt
>>> 10 zurück, mit welchen werten i wird die Schleife durchlaufen? und
>>> schaue, wo sie mehr straucheln.
>>
>> Die Antwort, die man hier lernt wäre, dass man das nicht sagen kann,
>> weil es implementation-defined ist. Und wenn im Body nicht irgendwas mit
>> Seiteneffekt steht sogar UB.
>
> Wo ist da etwas implementation-defined?

"mit welchen Werten" - das hängt von der Breite des unsigned ints ab. 
Implementation defined.
#6057070
Lesenswert?

Hallo,

also lesbarer wird es
1
for (unsigned int i = stdv.size()-1; i > 0; i--)
2
{
3
  std::cout << stdv[i];
4
}

Dann sieht man das die Wirkungsweise nur darauf zielt das die for 
Schleife um einmal weniger ausgeführt wird wie sonst üblich. Also ohne 
das Zuweisungs minus ein.

Am Ende könnte auch das dastehen, je nachdem womit i initialisiert wird.
Ohne Zusammenhang kann man darüber nur spekulieren.
1
for (unsigned int i = stdv.size(); i > 1; i--)
2
{
3
  std::cout << stdv[i];
4
}
#6057202
Lesenswert?

Veit D. schrieb:
> also lesbarer wird es
> for (unsigned int i = stdv.size()-1; i > 0; i--)
> {
>   std::cout << stdv[i];
> }

entspricht aber nicht dem Original!

h

Veit D. schrieb:
> Am Ende könnte auch das dastehen, je nachdem womit i initialisiert wird.
> Ohne Zusammenhang kann man darüber nur spekulieren.
> for (unsigned int i = stdv.size(); i > 1; i--)
> {
>   std::cout << stdv[i];
> }

Und das ist total falsch!

Und auch in dem ursprünglichen Code ist ein subtiler Fehler drin.

So ist es richtig:
1
    for (auto i{stdv.size()}; i-- > 0;) {
2
      std::cout << stdv[i];
3
    }

Den Fehler bekommt man übrigens auch angezeigt wenn man korrekt 
initialisieren und den falschen DT verwendet:
1
    for (unsigned int i{stdv.size()}; i-- > 0;) {
2
      std::cout << stdv[i];
3
    }
#6057352
Lesenswert?

Hallo,

ich habe das getestet, zeigt exakt das gleiche Verhalten.
1
const int var = 5;
2

3

4
void setup(void) {
5
  Serial.begin(9600);
6
  
7
  for (int i=var; i-- > 0;)
8
  {
9
     Serial.println(i);
10
  }
11
  
12
  Serial.println();
13
  
14
  for (int i=var-1; i>=0; i--)
15
  {
16
     Serial.println(i);
17
  }
18
}
19

20

21
void loop(void)
22
{
23
  
24
}
#6057398
Lesenswert?

Veit D. schrieb:
> du meinst den Unterschied zwischen signed und unsigned.

Jein.
Auch da passieren beliebig viele Fehler, wenn signed und unsigned Typen 
gemischt werden. Eigentlich ein sehr bekanntes Problem:

Schau Dir dies mal an, dann verstehst Du was ich meine:
1
    std::vector<char> v1(1024Ul * 1024UL * 1024UL * 5);
2

3
    int l1 = v1.size(); // Deine Variante
4
    std::cout << v1.size() << '\n';   
5
    std::cout << l1 << '\n'; 
6
    
7
    auto l2{v1.size()}; // Meine Variante
8
    std::cout << l2 << '\n';   
9

10
    int l3{v1.size()}; // Hier kommt eine Warnung
#6057426
Lesenswert?

Veit D. schrieb:
> du möchtest bestimmt mit int einen Überlauf und damit ein falsches
> Ergebnis provozieren.

Genau, Du hast es ;-)

Veit D. schrieb:
> Ich erhalte allerdings abgesehen von der Warnung
> 3x die Ausgabe 1073741824.

Ist die erste Zeile so, wie ich sie geschrieben habe? Vermutlich hast Du 
da schon den ersten Überlauf.

Für Größenangaben (Container-Größen, Elementanzahlen) sollte man immer 
size_t verwenden oder eben automatische Typinferenz. Ab C++20 kann man 
generell auch von den vorzeichenlosen Typen weg und verwendet c.ssize().
#6057458
Lesenswert?

Hallo,

also sollte normalerweise der erste Wert falsch sein bzw. von den 
anderen beiden abweichen?

Ich habe das in CodeBlocks 17.12 wie folgt ausprobiert, allerdings "nur" 
mit gcc 7.1. Das ist laut Buch frisch installiert. Normalerweise 
programmiere ich direkt für 8Bit Atmel µC und nicht am PC für den PC.
1
#include <iostream>
2
#include <vector>
3
using namespace std;
4

5
int main()
6
{
7
    cout << "Hello world!" << endl;
8

9
    std::vector<char> v1(1024UL * 1024UL * 1024UL * 5);
10

11
    int l1 = v1.size(); // Deine Variante
12
    std::cout << v1.size() << '\n';
13
    std::cout << l1 << '\n';
14

15
    auto l2{v1.size()}; // Meine Variante
16
    std::cout << l2 << '\n';
17

18
    // int l3{v1.size()}; // Hier kommt eine Warnung
19

20
    return 0;
21
}
#6059773
Lesenswert?

Hallo,

diese Zeile
1
decltype(std::vector<char>{}.size())::_;
verursacht diesen Fehlermeldung
decltype evaluates to 'std::vector<char>::size_type {aka unsigned int}', 
which is not a class or enumeration type|

und
1
int l3{v1.size()}; // Hier kommt eine Warnung
bringt folgende Warnung
narrowing conversion of 'v1.std::vector<char>::size()' from 
'std::vector<char>::size_type {aka unsigned int}' to 'int' inside { } 
[-Wnarrowing]|

Aber darum ging es mir nicht.
Es ging um diese Zeile
1
int l1 = v1.size(); // Deine Variante

hier müßte doch auf Grund vom Wertebereichüberlauf ein falsches Ergebnis 
erscheinen. Macht er nicht weil das Ergebnis in 32bit int passt. Bohre 
ich den Wert auf erhalte ich überall 0, was auch nicht sein kann.
1
#include <iostream>
2
#include <vector>
3
using namespace std;
4

5
int main()
6
{
7
    std::vector<char> v1(1024UL * 1024UL * 1024UL * 1024UL * 5);
8

9
    int l1 = v1.size(); // Deine Variante
10
    std::cout << v1.size() << '\n';
11
    std::cout << l1 << '\n';
12

13
    auto l2{v1.size()}; // Meine Variante
14
    std::cout << l2 << '\n';
15

16
    return 0;
17
}

Ergebnis 3x 0.

Also entweder habe ich 3x 1073741824 oder 3x 0. Kann beides nicht 
richtig sein. Verständlicher?
#6059782
Lesenswert?

Ok, Du arbeitest auf einem ILP32-System (32-Bit), da ist dann ein int 
und ein long unsigned int gleich mit 32-Bit.

Dann versuche folgendes:
1
    auto x1(std::numeric_limits<unsigned int>::max());
2
    int  x2(std::numeric_limits<unsigned int>::max());
3

4
    std::cout << x1 << '\n';    
5
    std::cout << x2 << '\n';
Gast #6059824
Lesenswert?

Wilhelm M. schrieb:
> mh schrieb:
>> Was kommt denn da für eine Warnung? Das müsste nen Fehler geben.
>
> Nein, kein Fehler. Sondern nur die "narrowing" Warnung.

Hmmm ist das nicht "list initialization"? Müsste dann nicht nen Fehler 
sein? Hier ein Auszug aus 
https://en.cppreference.com/w/cpp/language/list_initialization (hab grad 
keine Lust auf Standard lesen)
1
Otherwise (if T is not a class type), if the braced-init-list has only one element and either T isn't a reference type or is a reference type whose referenced type is same as or is a base class of the type of the element, T is direct-initialized (in direct-list-initialization) or copy-initialized (in copy-list-initialization), except that narrowing conversions are not allowed.

clang sagt, dass es nen error ist.
1
error: non-constant-expression cannot be narrowed from type 'std::vector::size_type' (aka 'unsigned long') to 'int' in initializer list [-Wc++11-narrowing]
2
    int l3{v1.size()}; // Hier kommt eine Warnung
#6059860
Lesenswert?

Veit D. schrieb:
> Bohre ich den Wert auf erhalte ich überall 0, was auch nicht sein kann.

Warum nicht?
1
>     std::vector<char> v1(1024UL * 1024UL * 1024UL * 1024UL * 5);
Hier läuft es wie zu erwarten ist über. 1024*1024*1024 gibt 2^30. Das 
passt in 32 Bit. Wenn man das mit 1024 multipliziert, bekommst du 2^40. 
Davon bleiben nur die unteren 32 Bit übrig, und die sind alle 0. Wenn 
man das dann mit 5 multipliziert, ist es immer noch 0. Es wird also ein 
vector mit 0 Elementen erzeugt. Dass dann für size() 0 zurückkommt, ist 
also zu erwarten.
#6059863
Lesenswert?

Hallo,

>
1
>     auto x1(std::numeric_limits<unsigned int>::max());
2
>     int  x2(std::numeric_limits<unsigned int>::max());
3
> 
4
>     std::cout << x1 << '\n';
5
>     std::cout << x2 << '\n';
6
>

das macht was es soll

4294967295
-1

Ich glaube wir haben jetzt genug getestet.
Problem war und ist klar dargelegt wurden.
Gast #6060059
Lesenswert?

Wilhelm M. schrieb:
> mh schrieb:
>> Hmmm ist das nicht "list initialization"?
>
> Nein, es ist brace-initialization, in diesem Fall als
> direct-scalar-initialization. Für Aggregate wäre es dann
> list-initialization.
>
> Dafür hat sich der Begriff "uniform initialization syntax" gebildet.


Soweit ich das überblicke handelt es sich um "direct 
list-initialization". direct-initialization nach 
http://eel.is/c++draft/dcl.init#16.1 und list-initialized nach 
http://eel.is/c++draft/dcl.init#17.1.

Und bei list-initialized trifft http://eel.is/c++draft/dcl.init#list-3.9 
zu. Dort gibt es dann auch ein Beispiel:
1
int x1 {2};                         // OK
2
int x2 {2.0};                       // error: narrowing
#6060205
Lesenswert?

mh schrieb:
> Soweit ich das überblicke handelt es sich um "direct
> list-initialization"

In

https://en.cppreference.com/w/cpp/language/direct_initialization

ist
1
auto x{42};

direct-initialization mit einem single brace enclosed initializer. Dies 
ist
eine Spezialform der list-initialization

https://en.cppreference.com/w/cpp/language/list_initialization

mit einer Liste mit einem Wert.

Die brace-initialization von Scalaren oder Aggregaten wird dann auch 
ugs. als "uniform initialization syntax" bezeichnet. Den Ausdruck finde 
ich sehr gut, denn diese Syntax ist wirklich universell, und tut das, 
was man erwartet, und warnt bzw. lehnt ein narrowing an/ab.
Gast #6060253
Lesenswert?

Wilhelm M. schrieb:
> direct-initialization mit einem single brace enclosed initializer. Dies
> ist
> eine Spezialform der list-initialization
> https://en.cppreference.com/w/cpp/language/list_initialization

Das ist genau das was ich geschriben hab. Und wenn man aus den 
beschriebenen Fällen, den einen Fall heraussucht, der hier zutrifft, 
findet man:
1
T object { arg1, arg2, ... };  (1)
2

3
List initialization is performed in the following situations:
4
 direct-list-initialization (both explicit and non-explicit constructors are considered)
5
  1) initialization of a named variable with a braced-init-list (that is, a 
6
  possibly empty brace-enclosed list of expressions or nested braced-init-lists)
7

8
Explanation
9
 The effects of list initialization of an object of type T are:
10
  Otherwise (if T is not a class type), if the braced-init-list has only one 
11
  element and either T isn't a reference type or is a reference type whose 
12
  referenced type is same as or is a base class of the type of the element, T 
13
  is direct-initialized (in direct-list-initialization) or copy-initialized 
14
  (in copy-list-initialization), except that narrowing conversions are not 
15
  allowed.

Also: narrowing conversions are not allowed
#6060262
Lesenswert?

mh schrieb:
> Das ist genau das was ich geschriben hab.

Sehr gut, da sind wir uns einig. Und Du hattest die bessere, 
standard-konforme Wortwahl!

mh schrieb:
> except that narrowing conversions are not
>   allowed.

Ich denke, dass ist nicht normativ. Sprich, der gcc darf ein mögliches 
narrowing warnen, und muss ein definitives narrowing mit Wertänderung 
als Fehler behandeln. Offensichtlich sieht das der clang anders (was mir 
besser gefällt).

Das sollte man (Du?) mal auf der isocpp ML klären.
Gast #6060275
Lesenswert?

Wilhelm M. schrieb:
> Sprich, der gcc darf ein mögliches
> narrowing warnen, und muss ein definitives narrowing mit Wertänderung
> als Fehler behandeln.

Ob, der tatsächliche Wert in den Typ passt, ist egal. In beiden Fällen 
ist es laut Standard ill-formed 
http://eel.is/c++draft/dcl.init#list-7.4. Er muss also in beiden fällen 
nur warnen, darf aber einen Fehler erzeugen.

> Das sollte man (Du?) mal auf der isocpp ML klären.

Das Probem ist eher der gcc. Und ich vermute, die haben das mit Absicht 
gemacht.
#6060464
Lesenswert?

Rolf M. schrieb:
> Veit D. schrieb:
>> Bohre ich den Wert auf erhalte ich überall 0, was auch nicht sein kann.
>
> Warum nicht?

Hallo Rolf,

dich hatte ich komplett übersehen. Sorry.
Okay. Warum habe ich was anderes erwartet?
Weil mir nachwievor die Differenz als falsches Ergebnis fehlt.
1
uint32_t var1 = (1024UL * 1024UL * 1024UL * 5);
2
std::cout << var1 << '\n';
3

4
uint16_t var2 = 1024UL * 1024UL * 1024UL * 5;
5
std::cout << var2 << '\n';

Ausgabe:

1073741824
0


Ich rechne nochmal.
1
1024 * 1024 * 1024 * 5 = 5.368.709.120
Wertebereich von uint32 ist 0 ... 4.294.967.296
Die Differenz ist 1073741824. Das ist der Rest vom Überlauf. Wie 
erwartet.

Wertebereich von uint16 ist 0 ... 65.536
Der Wert 65.536 passt 81920 mal rein. Rest 0.

:-)  :-)  :-)

Alles klar, habe micht selbst geschlagen.
Ich hatte nicht erwartet das es beim letzen Überlauf ohne Rest aufgeht.

Jetzt bin ich glücklich mit mir selbst.   :-)

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren