Manchmal muss man ja solche Ketten-Vergleiche (s.u.) hinschreiben. Das
ist zwar kein Problem, auch nicht wirklich ein Lesbarkeitsproblem, aber
schön ist es auch nicht. Hinzu kommt aber ein semantisches Thema bei
volatile DT, dass die Teilausdrücke der Bedingung evtl. mit
unterschiedlichen Laufzeit-Werten arbeiten, da in jedem Teilausdruck
erneut das volatile Objekt ausgewertet wird.
Das kann man zwar etwa mittels eines variadischen templates lösen:
1
if(all_of{x,y,z}.less(v1)){
2
// ...
3
}
4
if(each_of_of{x,y,z}.not_equal_to(v1)){
5
// ...
6
}
oder
1
if(all_of{x,y,z}(std::less{})(v1)){
2
// ...
3
}
Aber schön ist auch das nicht wirklich.
Deswegen habe ich das mal wie unten (!PLAIN) realisiert (Danke B.
Fahller). Die templates any_of<> und each_of<> sind im namespace Dry:
das ist alles in der angehängten Datei (c++2a).
das erinnert mich immer an den ersten Apple Drucker.
für die Demo haben die Jungs alle Schleifen und Prozeduren entpackt und
linearisiert im Druckprogramm
einfach um die damals "sagenhafte Druckgeschwindigkeit" zu
realisieren...
Manchmal ist eine kleine Wiederholung schneller als ein call... noch
immer!
Aber ja, ich mag die Idee dennoch;
kommt mir sehr so vor als hätte ich vor einigen Monaten exakt sowas
gebrauchen können weil die lesbarkeit eben doch zu leiden begann
(ohne nochmal nachzublättern)
Also: Dankeschön!
(kann ja als Gast keine Daumen/Sternchen/Empfehlungsgedingse verteilen
;))
sid schrieb:> Manchmal ist eine kleine Wiederholung schneller als ein call... noch> immer!
Meinst Du Laufzeit?
Der generierte Assembler Code ist identisch zu der PLAIN Variante, wenn
die Objekte non-volatile sind.
Im Falle von volatile Objekten ist die non-PLAIN, also die Dry-Variante
kürzer. Dies ist natürlich klar, weil die Mehrfachauswertung entfällt.
In meiner Ansicht ist dies dann auch die richtige Semantik.
Für
1
volatileuint8_tx{1};
2
uint8_ty{2};
3
uint8_tz{3};
4
volatileuint8_tv1{2};
5
6
#define PLAIN
7
8
intmain(){
9
usingnamespaceDry;
10
uint8_tr{};
11
#ifdef PLAIN
12
if((v1<x)||(v1<y)||(v1<z)){
13
r+=1;
14
}
15
#else
16
if(v1<any_of{x,y,z}){
17
r+=1;
18
}
19
#endif
20
returnr;
21
}
wird bei PLAIN daraus (der Einfachheit in AVR-Assembler):
1
main:
2
ldsr25,v1;v1.2_1,v1
3
ldsr24,x;x.3_2,x
4
cpr25,r24;v1.2_1,x.3_2
5
brlo.L5;,
6
ldsr25,v1;v1.4_3,v1
7
ldsr24,y;y,y
8
cpr25,r24;v1.4_3,y
9
brsh.L7;,
10
.L5:
11
ldir24,lo8(1);r,
12
.L2:
13
ldir25,0;
14
ret
15
.L7:
16
ldsr18,v1;v1.6_5,v1
17
ldir24,lo8(1);r,
18
ldsr25,z;z,z
19
cpr18,r25;v1.6_5,z
20
brlo.L2;,
21
ldir24,0;r
22
rjmp.L2;
23
.sizemain,.-main
und in der Dry Variante (!PLAIN):
1
main:
2
ldsr24,x;x.1_1,x
3
ldsr18,y;y.2_2,y
4
ldsr19,z;z.3_3,z
5
ldsr25,v1;_7,v1
6
cpr25,r24;_7,x.1_1
7
brlo.L4;,
8
ldir24,lo8(1);r,
9
cpr25,r18;_7,y.2_2
10
brsh.L7;,
11
.L2:
12
ldir25,0;
13
ret
14
.L4:
15
ldir24,lo8(1);r,
16
rjmp.L2;
17
.L7:
18
cpr25,r19;_7,z.3_3
19
brlo.L2;,
20
ldir24,0;r
21
rjmp.L2;
22
.sizemain,.-main
Man sieht also, dass v1 nur einmal ausgewertet wird. In der PLAIN
Variante wäre das dann etwa so möglich:
1
if(autovv=v1;(vv<x)||(vv<y)||(vv<z)){
2
r+=1;
3
}
Lesbarer wird es dadurch m.E. nicht.
BTW: angehängt ist noch eine funktional geschriebene Variante von
any_of() und each_of(). Beim Aufruf beachten, dass hier any_of(x, y)
statt any_of{x, y} geschrieben werden muss, weil hier ein Closure
aufgefufen wird statt eines ctors.
Man muss Björn Fahller einfach mögen, der Herr gibt fantastische Talks.
Ich hab ihn zwar noch nicht gesehen, aber folgender Talk(?) dürfte
diesen Thread hier abdecken:
https://youtu.be/YUWuNpxZa5k
Beide Beispiele funktionieren out-of-the-box übrigens nur im aktuellen
GCC trunk.
Was ich interessant find ist dass Clang sich in der 1.Variante über
folgenden deduction guide beschwert
Gibts da bezüglich C++20 irgendwelche Änderungen?
Wieso geht das beim GCC?
Auch in der 2.Variante muss man für Clang momentan 2 Template Parameter
extra übergeben. So schluckt Clang folgendes nicht
Ich persönlich finde die funktionale Variante übrigens wesentlich
schöner und vor allem besser lesbar. Auch schaun die geschwungenen
Klammern im if irgendwie komisch aus... (first world problems) ;)
Wilhelm M. schrieb:> Man sieht also, dass v1 nur einmal ausgewertet wird. In der PLAIN> Variante wäre das dann etwa so möglich:
Du zeigst eine Alternative und verkaufst als Vorteil, dass sie volatile
ignoriert?
Vincent H. schrieb:> Gibts da bezüglich C++20 irgendwelche Änderungen?> Wieso geht das beim GCC?
Ich denke, das liegt daran, dass clang CTAD für alias-templates noch
nicht implementiert hat (wie in P1021R4).
Hattest Du Optimierungen bei dem plain-Code aktiviert?
Zu der Frage des volatile würde ich gerne mal folgende These in den Raum
stellen:
OK. Volatile soll den Variablenwert immer wieder neu laden, weil er sich
verändert haben könnte.
Andererseits - so sehe ich das - würde man bei solchen Massenvergleichen
in der Regel die Absicht haben den Zustand zu einem bestimmten Zeitpunkt
zu verwerten und also - weiter meine Ansicht - zunächst die volatile
Variabel in eine temporäre kopieren, und die Vergleiche mit dieser
temporären Variablen anstellen.
So hast Du es ja in der plain-Variante von 04.01.2020 07:20 auch
geschrieben.
Soweit, so gut.
Dann aber schreibst Du:
> Dies ist natürlich klar, weil die Mehrfachauswertung entfällt.
Ich weiß nicht ob ich Dich recht verstehe (und bin auch in C++ nicht
fit), aber das hört sich so an, als wenn in Deiner Dry-Version )inC++)
bei volatile Variablen eine Mehrfachauswertung unvermeidbar ist.
Verstehe ich das richtig? Wenn ja warum? (Kann man das jemandem
erklären, der C++ nur oberflächlich kann). Oder gibt es andersherum eine
Möglichkeit, das selbe Spiel (volatiles umkopieren) auch in der
DRY-Version zu machen?
Theor schrieb:> Andererseits - so sehe ich das - würde man bei solchen Massenvergleichen> in der Regel die Absicht haben den Zustand zu einem bestimmten Zeitpunkt> zu verwerten und also - weiter meine Ansicht - zunächst die volatile> Variabel in eine temporäre kopieren, und die Vergleiche mit dieser> temporären Variablen anstellen.> So hast Du es ja in der plain-Variante von 04.01.2020 07:20 auch> geschrieben.
Genau: Wobei das strenggenommen nicht ausreichend ist, weil das ein
atomarer DT sein müsste. Bei AVR wäre das nur uint8_t, char, std::byte,
etc.
Theor schrieb:> Ich weiß nicht ob ich Dich recht verstehe (und bin auch in C++ nicht> fit), aber das hört sich so an, als wenn in Deiner Dry-Version )inC++)> bei volatile Variablen eine Mehrfachauswertung unvermeidbar ist.
Nein, genau anders herum: die Schreibweise
1
if(v<any_of(x,y,z)){
2
...
3
}
suggeriert ja gerade, dass v nur einmal ausgewertet wird. Und dem ist
auch so, da die LHS des op< kopiert wird. Natürlich mit derselben
Einschränkung: ggf. fehlende Atomarität, um die man sich ggf. kümmern
muss.
@ Wilhelm M. (wimalopaan)
Hm. OK. Ich denke, ich weiß was mich irritiert.
Die unter PLAIN und !PLAIN gezeigten Codes (bis zu der Variante mit auto
in dem Beitrag 04.01.2020 07:20, haben dann nämlich nicht identische
Wirkung, eben weil erst in der auto-Variante das umkopieren enthalten
ist.
Stimmst Du zu?
Das ist nun nichts Weltbewegendes und entwertet Deine Arbeit nicht, aber
ich mag es gerne klar. Ich bin - wegen meiner geringen C++-Kenntnisse
fälschlich - davon ausgegangen, dass die Dry-Variante die Variable auch
mehrfach auswertet. Irgendwas hatte mich aber dann zum gedanklichen
stolpern gebracht.
OK. Danke erstmal für Deine Antwort, Wilhelm
P.S. Ja. Das ist klar, mit längeren Variablen und atomaren Transfers.
Aber danke für den Hinweis.
Theor schrieb:> Die unter PLAIN und !PLAIN gezeigten Codes (bis zu der Variante mit auto> in dem Beitrag 04.01.2020 07:20, haben dann nämlich nicht identische> Wirkung, eben weil erst in der auto-Variante das umkopieren enthalten> ist.> Stimmst Du zu?
Ja.
Theor schrieb:> Ich bin - wegen meiner geringen C++-Kenntnisse> fälschlich - davon ausgegangen, dass die Dry-Variante die Variable auch> mehrfach auswertet.
Zu diesem Schluß könntest Du eigentlich nur gekommen sein, wenn Du Dir
die Implementierung ansiehst und hierbei einen gedanklichen Fehler
machst.
An der Aufrufstelle deutet nichts auf eine Mehrfachauswertung hin, und
so soll es sein: ganz klar.
Wilhelm M. schrieb:> Theor schrieb:>> Die unter PLAIN und !PLAIN gezeigten Codes [...]>> Stimmst Du zu?>> Ja.
Ah. OK.
> Theor schrieb:>> Ich bin [...]>> Zu diesem Schluß könntest Du eigentlich nur gekommen sein, wenn Du Dir> die Implementierung ansiehst und hierbei einen gedanklichen Fehler> machst.> An der Aufrufstelle deutet nichts auf eine Mehrfachauswertung hin, und> so soll es sein: ganz klar.
Das nehme ich mal so hin. Ich fürchte, wenn wir anfangen, das
auseinander zu klamüsern, enden wir damit, dass Du mir C++ beibringst.
:-)
Wilhelm M. schrieb:> Deswegen habe ich das mal wie unten (!PLAIN) realisiert (Danke B.> Fahller). Die templates any_of<> und each_of<> sind im namespace Dry:> das ist alles in der angehängten Datei (c++2a).
gcc 9.2.0 mit std=c++2a meint dazu:
..\main.cpp:95:20: error: missing template arguments before '{' token
95 | if (v1 < any_of{x, y, z}) {
Mit
Nachtrag:
clang 9.0.0 sagt dazu:
..\main.cpp:96:14: error: alias template 'any_of' requires template
arguments; argument deduction only allowed for class templates
(und meckert über "using base::tuple;", aber das kann auch ein clang-bug
sein)
Oliver
Es ist nicht wirklich ein Bug von clang. Nur ist clang bzgl. CTAD für
alias templates etwas hinterher. Deswegen brauch clang etwas mehr
boilerplate.
Der angehängte Code ght nun für gcc >= 8.1 und clang >= 9.0.
Oliver S. schrieb:> Ok. Bleibt nur noch die Frage, warum das nötig ist...
Na, weil auf der linken Seite in diesem Fall ein primitiver DT steht.
Man könnte auch ggf. ein einstelligen ctor als Typwandlungs-ctor
zulassen, und dann würde man nach der alten Regel verfahren, dass die
binären Operatoren symmetrisch als freie Funktion geschrieben werden
sollen.
Ich habe hier ja auch z.B. op!= nicht generieren lassen, sondern wegen
der Semantik selbst definiert. Ab C++20 ist ja auch das automatische
Generieren der sekundären op !=, < <=, > >= durch die primären op== und
op<=> möglich, aber das passt hier ja semantisch nicht bzw. der
spaceship-op lässt sich nicht falten.
Wilhelm M. schrieb:> Na, weil auf der linken Seite in diesem Fall ein primitiver DT steht.
Die Frage war eher, warum es nur bei == nicht geht, aber das hatte ich
dann auch schon selber rausgefunden. Da war dir was bei copy-paste
durchgerutscht ;)
Für die lambdas in apply() brauche ich wohl noch etwas, daß muß ich mit
mal in Ruhe anschauen, wenn ich etwas mehr Zeit habe.
Oliver