Forum: Projekte & Code Dry made easy


von Wilhelm M. (wimalopaan)


Angehängte Dateien:

Lesenswert?

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).
1
volatile uint8_t x{1};
2
uint8_t y{2};
3
uint8_t z{3};
4
volatile uint8_t v1{2};
5
6
//#define PLAIN
7
8
int main() {
9
    using namespace Dry;
10
    uint8_t r{};
11
#ifdef PLAIN
12
    if ((v1 < x) || (v1 < y) || (v1 < z)) {
13
        r += 1;
14
    }
15
    if ((x == v1) || (y == v1) || (z == v1)) {
16
        r += 2;
17
    }
18
    if ((v1 != x) || (v1 != y) || (v1 != z)) {
19
        r += 3;
20
    }
21
    if ((v1 != x) && (v1 != z)) {
22
        r += 10;
23
    }
24
#else
25
    if (v1 < any_of{x, y, z}) {
26
        r += 1;
27
    }
28
    if (any_of{x, y, z} == v1) {
29
        r += 2;
30
    }
31
    if (v1 != any_of{x, y, z}) {
32
        r += 3;
33
    }
34
    if (v1 != each_of{x, z}) {
35
        r += 10;
36
    }
37
#endif
38
    return r;
39
}

von Theor (Gast)


Lesenswert?

Interessant.


Nebenbei: Was bedeutet "Dry"? Der Titel klingt fast, als müsste man 
"Dry" kennen.

von Wilhelm M. (wimalopaan)


Lesenswert?

Dry: Don't repeat yourself

von Theor (Gast)


Lesenswert?

Wilhelm M. schrieb:
> Dry: Don't repeat yourself

Ah. Danke.

von sid (Gast)


Lesenswert?

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 
;))

von Jemand2 (Gast)


Lesenswert?

Wenn da irgendwas schiefgeht, würde ich den Bug nicht suchen wollen.
KISS

von Wilhelm M. (wimalopaan)


Angehängte Dateien:

Lesenswert?

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
volatile uint8_t x{1};
2
uint8_t y{2};
3
uint8_t z{3};
4
volatile uint8_t v1{2};
5
6
#define PLAIN
7
8
int main() {
9
    using namespace Dry;
10
    uint8_t r{};
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
    return r;
21
}

wird bei PLAIN daraus (der Einfachheit in AVR-Assembler):
1
main:
2
lds r25,v1       ;  v1.2_1, v1
3
lds r24,x        ;  x.3_2, x
4
cp r25,r24               ;  v1.2_1, x.3_2
5
brlo .L5                 ; ,
6
lds r25,v1       ;  v1.4_3, v1
7
lds r24,y        ;  y, y
8
cp r25,r24               ;  v1.4_3, y
9
brsh .L7                 ; ,
10
.L5:
11
ldi r24,lo8(1)   ;  r,
12
.L2:
13
ldi r25,0                ;
14
ret
15
.L7:
16
lds r18,v1       ;  v1.6_5, v1
17
ldi r24,lo8(1)   ;  r,
18
lds r25,z        ;  z, z
19
cp r18,r25               ;  v1.6_5, z
20
brlo .L2                 ; ,
21
ldi r24,0                ;  r
22
rjmp .L2                 ;
23
.size   main, .-main

und in der Dry Variante (!PLAIN):
1
main:
2
lds r24,x        ;  x.1_1, x
3
lds r18,y        ;  y.2_2, y
4
lds r19,z        ;  z.3_3, z
5
lds r25,v1       ;  _7, v1
6
cp r25,r24               ;  _7, x.1_1
7
brlo .L4                 ; ,
8
ldi r24,lo8(1)   ;  r,
9
cp r25,r18               ;  _7, y.2_2
10
brsh .L7                 ; ,
11
.L2:
12
ldi r25,0                ;
13
ret
14
.L4:
15
ldi r24,lo8(1)   ;  r,
16
rjmp .L2                 ;
17
.L7:
18
cp r25,r19               ;  _7, z.3_3
19
brlo .L2                 ; ,
20
ldi r24,0                ;  r
21
rjmp .L2                 ;
22
.size   main, .-main

Man sieht also, dass v1 nur einmal ausgewertet wird. In der PLAIN 
Variante wäre das dann etwa so möglich:
1
    if (auto vv = 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.

von Vincent H. (vinci)


Lesenswert?

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
1
template <typename O, typename... T> op_t(T&& ...) -> op_t<O, T...>;

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
1
constexpr auto any_of = [](auto... ts){return op_t{and_f, tuple(ts...)};};
2
constexpr auto each_of = [](auto... ts){return op_t{or_f, tuple(ts...)};};

sondern erst die komplett ausgeschriebene Variante
1
constexpr auto any_of = [](auto... ts) {
2
  return op_t<decltype(and_f), decltype(tuple(ts...))>{and_f, tuple(ts...)};
3
};
4
constexpr auto each_of = [](auto... ts) {
5
  return op_t<decltype(or_f), decltype(tuple(ts...))>{or_f, tuple(ts...)};
6
};

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) ;)

von A. S. (Gast)


Lesenswert?

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?

von Wilhelm M. (wimalopaan)


Lesenswert?

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).

von Theor (Gast)


Lesenswert?

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?

von Theor (Gast)


Lesenswert?

Ups.

Die letzte Frage richtete sich an Wilhelm M. (wimalopaan)

:-)

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von Theor (Gast)


Lesenswert?

@ 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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von Theor (Gast)


Lesenswert?

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. 
:-)

von Oliver S. (oliverso)


Lesenswert?

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
1
    if (v1 < any_of<volatile uint8_t,uint8_t, uint8_t>{x, y, z}) {

compiliert es.

Ist wohl noch etwas zu sehr "bleeding edge" ...

Oliver

von Oliver S. (oliverso)


Lesenswert?

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

: Bearbeitet durch User
Beitrag #6097927 wurde vom Autor gelöscht.
von Wilhelm M. (wimalopaan)


Angehängte Dateien:

Lesenswert?

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.

von Oliver S. (oliverso)


Lesenswert?

Ein lezter complaint, weil unschön:

== compiliert nicht für
1
if (v1 == any_of{x, y, z}) {

sondern nur anders herum, alle anderen Operatoren aber schon.
Ja, ist meckern auf hohem Niveau, aber wenn schon, denn schon...

Oliver

von Wilhelm M. (wimalopaan)


Lesenswert?

Oliver S. schrieb:
> == compiliert nicht für
> if (v1 == any_of{x, y, z}) {

Dann füge die Permutation hinzu:
1
        template <typename U>
2
        friend inline constexpr bool operator==(U&& u, op_t&& a) {
3
            return std::forward<op_t>(a) == std::forward<U>(u);            
4
        }

: Bearbeitet durch User
von Oliver S. (oliverso)


Lesenswert?

Danke

Oliver

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von Oliver S. (oliverso)


Lesenswert?

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

von Vincent H. (vinci)


Lesenswert?


von Heiko L. (zer0)


Lesenswert?

Warum "each_of" und nicht "all_of"?
"none_of" fehlt :)

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.