Forum: Projekte & Code Dry made easy


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von Wilhelm M. (wimalopaan)


Angehängte Dateien:

Bewertung
2 lesenswert
nicht 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:
if (all_of{x, y, z}.less(v1)) {
   // ...
}
if (each_of_of{x, y, z}.not_equal_to(v1)) {
   // ...
}

oder
if (all_of{x, y, z}(std::less{})(v1)) {
   // ...
}

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).
volatile uint8_t x{1};
uint8_t y{2};
uint8_t z{3};
volatile uint8_t v1{2};

//#define PLAIN

int main() {
    using namespace Dry;
    uint8_t r{};
#ifdef PLAIN
    if ((v1 < x) || (v1 < y) || (v1 < z)) {
        r += 1;
    }
    if ((x == v1) || (y == v1) || (z == v1)) {
        r += 2;
    }
    if ((v1 != x) || (v1 != y) || (v1 != z)) {
        r += 3;
    }
    if ((v1 != x) && (v1 != z)) {
        r += 10;
    }
#else
    if (v1 < any_of{x, y, z}) {
        r += 1;
    }
    if (any_of{x, y, z} == v1) {
        r += 2;
    }
    if (v1 != any_of{x, y, z}) {
        r += 3;
    }
    if (v1 != each_of{x, z}) {
        r += 10;
    }
#endif
    return r;
}

von Theor (Gast)


Bewertung
2 lesenswert
nicht lesenswert
Interessant.


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

von Wilhelm M. (wimalopaan)


Bewertung
0 lesenswert
nicht lesenswert
Dry: Don't repeat yourself

von Theor (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Wilhelm M. schrieb:
> Dry: Don't repeat yourself

Ah. Danke.

von sid (Gast)


Bewertung
0 lesenswert
nicht 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)


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

von Wilhelm M. (wimalopaan)


Angehängte Dateien:

Bewertung
2 lesenswert
nicht 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
volatile uint8_t x{1};
uint8_t y{2};
uint8_t z{3};
volatile uint8_t v1{2};

#define PLAIN

int main() {
    using namespace Dry;
    uint8_t r{};
#ifdef PLAIN
    if ((v1 < x) || (v1 < y) || (v1 < z)) {
        r += 1;
    }
#else
    if (v1 < any_of{x, y, z}) {
        r += 1;
    }
#endif
    return r;
}

wird bei PLAIN daraus (der Einfachheit in AVR-Assembler):
main:
lds r25,v1       ;  v1.2_1, v1
lds r24,x        ;  x.3_2, x
cp r25,r24               ;  v1.2_1, x.3_2
brlo .L5                 ; ,
lds r25,v1       ;  v1.4_3, v1
lds r24,y        ;  y, y
cp r25,r24               ;  v1.4_3, y
brsh .L7                 ; ,
.L5:
ldi r24,lo8(1)   ;  r,
.L2:
ldi r25,0                ;
ret
.L7:
lds r18,v1       ;  v1.6_5, v1
ldi r24,lo8(1)   ;  r,
lds r25,z        ;  z, z
cp r18,r25               ;  v1.6_5, z
brlo .L2                 ; ,
ldi r24,0                ;  r
rjmp .L2                 ;
.size   main, .-main

und in der Dry Variante (!PLAIN):
main:
lds r24,x        ;  x.1_1, x
lds r18,y        ;  y.2_2, y
lds r19,z        ;  z.3_3, z
lds r25,v1       ;  _7, v1
cp r25,r24               ;  _7, x.1_1
brlo .L4                 ; ,
ldi r24,lo8(1)   ;  r,
cp r25,r18               ;  _7, y.2_2
brsh .L7                 ; ,
.L2:
ldi r25,0                ;
ret
.L4:
ldi r24,lo8(1)   ;  r,
rjmp .L2                 ;
.L7:
cp r25,r19               ;  _7, z.3_3
brlo .L2                 ; ,
ldi r24,0                ;  r
rjmp .L2                 ;
.size   main, .-main

Man sieht also, dass v1 nur einmal ausgewertet wird. In der PLAIN 
Variante wäre das dann etwa so möglich:
    if (auto vv = v1; (vv < x) || (vv < y) || (vv < z)) {
        r += 1;
    }

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)


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

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

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


Bewertung
-3 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht lesenswert
Ups.

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

:-)

von Wilhelm M. (wimalopaan)


Bewertung
0 lesenswert
nicht 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
if (v < any_of(x, y, z)) {
...
}

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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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
    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)


Bewertung
0 lesenswert
nicht 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:

Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht lesenswert
Ein lezter complaint, weil unschön:

== compiliert nicht für
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)


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

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

: Bearbeitet durch User
von Oliver S. (oliverso)


Bewertung
0 lesenswert
nicht lesenswert
Danke

Oliver

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht 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)


Bewertung
0 lesenswert
nicht lesenswert

von Heiko L. (zer0)


Bewertung
0 lesenswert
nicht lesenswert
Warum "each_of" und nicht "all_of"?
"none_of" fehlt :)

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.