Mehrstellige Funktionen mit unspezifischen Parametertypen zu verwenden,
ist nie eine gute Idee, wie etwa in:
1
Dated{1,2,3};// mmh?
Denn für den Aufrufer ist einfach unklar, was die Argumente bedeuteten
(welches Kalender-Datum).
Zwei gängige Lösungen für derartige Probleme existieren mit
domänenspezifischen Parametertypen (1) oder mit dem
named-parameter-idiom (2):
1
Dated{Day{1],Month{2},Century{20}.year{3}};// (1)
2
3
Dated{Day{}.day(1).month(2).year(1903)};// (2)
Bei (2) kann der Anwender die "Parameterreihenfolge" beliebig
permutieren. Bei (1) muss das durch Permutation der Parameterliste der
aufgerufenen Funktion vorgesehen sein, was oft für den Implementierer
(hier: der Klasse Date) durch zig-fache Überladung unschön wird.
Andererseits ist der Einsatz von (2) auf Elementfunktionen beschränkt.
Eine universelle Lösung erreicht man, wenn man die Parameterliste einer
Funktion als (heterogenes) Tuple auffasst. Für dieses Parametertuple
wird festgelegt, welche Datentypen enthalten sein müssen, aber nicht in
welcher Reihenfolge. Somit kann der Aufrufer seine Argumente beliebig
permutieren.
Beispiel:
1
intmain(){
2
foo(A{1},B{2});// every permutation possible
3
foo(B{1},A{2});
4
foo(B{1},A{2},C{3});// every permutation possible
5
foo(B{1},C{3},A{3});
6
// foo(1, 2); // not possible
7
8
constexprDated1{Year{1903},Day{1},Month{3}};// every permutation possible
9
constexprDated2{Day{1},Month{3},Year{1903}};
10
11
static_assert(d1==d2);
12
}
Der simpelste Ansatz dazu ist etwa:
1
template<typename...TT>
2
uint8_tsimple(TT...vv){// at least two parameters of type A and type B are neccessary
3
autoa=std::get<A>(std::tuple{vv...});
4
autob=std::get<B>(std::tuple{vv...});
5
returna.value+b.value;
6
}
Diese Funktion kann verwendet werden:
1
simple(A{2},B{1});// ok
2
simple(B{1},A{2});// ok
3
simple(B{1},A{2},C{3});// not intentional
wobei die letzte Zeile nicht möglich sein sollte. Zudem beziehen sich
ggf. die Fehlermeldung auf die Implementierung der Funktion und nicht
auf ihre Schnittstelle.
Die weitere Idee besteht nun darin, die Parameterliste mit
entsprechenden Constraints auszustatten, so dass nur die gewünschten
Parametertypen erlaubt sind, aber trotzdem auch ein Überladen möglich
ist.
Im folgenden Beispiel beschränkt der Constraint requires(...) das
Parametertyptuple auf die Typen A, B und C:
Aufgerufen werden kann die Funktion mit einer beliebigen Permutation der
Argumente (s.a. Beispiel oben).
Das Ganze hat etwas von K&R-Style für Parameter zusammen mit variablen
Parameterlisten.
Der gesamte Code ist in der angehängten Datei.
Die kleinen Meta-Programmierungsfunktion aus dem namespace Meta kann man
geren durch boost::hana oder dgl. ersetzten.
Im namespace Parameter ist das entscheidene Variablentemplate is<...>
untergebracht wie auch ein paar convenience-Funktionen.
Eine relativ einfache Lösung für dieses Defizit* von C++. Es löst das
Problem aber auch nur Teilweise. Sobald man mehrere gleichartige Werte
benötigt steht man wieder bei 0 oder muss noch mehr Wrapper einführen.
Nehmen wir als Beispiel std::copy. War es
1
std::copy(QuelleStart,QuelleEnde,ZielStart)
oder doch eher wie bei memcpy
1
std::copy(ZielStart,QuelleStart,QuelleEnde)
oder sollte es vielleicht ein
1
std::copy(QuelleStart,ZielStart,ZielEnde)
geben? Wäre nett, wenn alle Varianten zur Verfügung stehen.
In einigen Fällen könnte man diese Probleme auch einfach vermeiden.
Warum halten wir uns nicht alle an die ISO und benutzen immer und
überall YYYY-MM-DD fürs Datum? ;-)
Diese Strong Types, die du für Datum benutzt, helfen leider auch nur
Teilweise beim vermeiden von Fehlern.
Statt
Hunderte neuer Datentypen einzuführen, nur um bei Funktionsaufrufen die
Argumentreihenfolge überprüfbar zu machen, halte ich für übertrieben und
kaum praktikabel. Nur wenn die Datentypen bereits für andere Zwecke
benötigt werden, sie also sowieso schon vorhanden sind, würde ich sie
auch zur engeren Typisierung der Funktionsargumente verwenden. Aber nur
dann.
Konsequent durchgezogen werden kann dieses Konzept ohnehin nicht, da es
auch in weiten Teilen der Standardbibliothek nicht umgesetzt wird.
gibt es in den neueren c++ standards nicht eigene literale für
Zeiteinheiten?
dann könnte die funktion einfach so aussehen:
void setTime(Timeval v1);
setTime(12_h + 4_m + 13_s + 3252_us);
(nur vom prinzip - die genauen Datentypen, Suffixe und Syntaxen hab ich
gerade nicht parat)
gleiches Prinzip natürlich mit Daten
void setDate(Dateval v1);
setDate(1982_Y + 3_M + 14_D);
Vlad T. schrieb:> gibt es in den neueren c++ standards nicht eigene literale für> Zeiteinheiten?
Wenn es nur im Literale geht, kann man das Problem mit der Sortierung
der Argumente einfach lösen
mh schrieb:> Wenn es nur im Literale geht, kann man das Problem mit der Sortierung> der Argumente einfach lösenDate d{"2019-01-10"}
Das ist ein String-Literal und kein Zeit/Dauer-Literal.
Gemeinst ist dies hier:
https://howardhinnant.github.io/date/date.html
Yalu X. schrieb:> Hunderte neuer Datentypen einzuführen, nur um bei Funktionsaufrufen die> Argumentreihenfolge überprüfbar zu machen, halte ich für übertrieben und> kaum praktikabel. Nur wenn die Datentypen bereits für andere Zwecke> benötigt werden, sie also sowieso schon vorhanden sind, würde ich sie> auch zur engeren Typisierung der Funktionsargumente verwenden. Aber nur> dann.>> Konsequent durchgezogen werden kann dieses Konzept ohnehin nicht, da es> auch in weiten Teilen der Standardbibliothek nicht umgesetzt wird.
Find ich nicht. So einen Datentypen anzulegen ist ja nur eine Zeile
Konsequent durchziehen heißt für mich diese Typen in Interfaces zu
nutzen. Was irgendwelche Module und Klassen-Interna damit anstellen ist
mir als Nutzer dann egal.
Übrigens sind diese sogenannten Strong Types ein ausgezeichnetes
Beispiel für moderne Optimierungen. Code der davon gebraucht macht wird
meist kleiner und schneller (Stichwort aliasing).
Wilhelm M. schrieb:> Geht so, nur schöner ;-)Wilhelm M. schrieb:> https://howardhinnant.github.io/date/date.html
stimmt, den Divisionsoperator anstelle Addition zu benutzen sieht
schöner aus, fühlt sich aber auch unintuitiver an, weils ja eigentlich
ne addition ist ;)
Aber wenn du das weißt, versteh ich deine Frage nicht.
Genau analog kannst du das bei dir doch dann auch umsetzen.
Eine Funktion mit x Argumenten ist unnötig, du lässt den Typ die
Umrechenarbeit machen und nimmst nur das Resultat
Yalu X. schrieb:> Hunderte neuer Datentypen einzuführen, nur um bei> Funktionsaufrufen die> Argumentreihenfolge überprüfbar zu machen, halte ich für übertrieben und> kaum praktikabel. Nur wenn die Datentypen bereits für andere Zwecke> benötigt werden, sie also sowieso schon vorhanden sind, würde ich sie> auch zur engeren Typisierung der Funktionsargumente verwenden. Aber nur> dann.>> Konsequent durchgezogen werden kann dieses Konzept ohnehin nicht, da es> auch in weiten Teilen der Standardbibliothek nicht umgesetzt wird.
So sehe ich das auch. Es ist schade, dass die Sprache kein "a=b" in
Funktionsaufrufen zur Verfügung stellt, aber damit muss man eben leben
und keine der vorgeschlagenen Optionen ist eine Verbesserung der
Situation.
Eher würde ich mir Gedanken machen über
- evtl. sollten die Funktionsargumente in einem struct gruppiert sein?
- gibt es eine "logische" Reihenfolge die immer gleich ist?
Wilhelm M. schrieb:> mh schrieb:>> Wenn es nur im Literale geht, kann man das Problem mit der Sortierung>> der Argumente einfach lösenDate d{"2019-01-10"}>> Das ist ein String-Literal und kein Zeit/Dauer-Literal.>> Gemeinst ist dies hier:>> https://howardhinnant.github.io/date/date.html
Das ist mir klar. Ich wollte damit nur deutlich machen, dass es
Alternativen gibt zu
Vlad T. schrieb:> setDate(1982_Y + 3_M + 14_D);
und
Wilhelm M. schrieb:> Date d{1, 2, 3}; // mmh?> Date d{Day{1], Month{2}, Century{20}.year{3}}; // (1)> Date d{Day{}.day(1).month(2).year(1903)}; // (2)
solange es nur um Literale geht. Und ich finde ein Literal der Form
"2019-01-10"
lesbarer als die voherigen Alternativen. Vor allem wenn noch die
Uhrzeit, TZ und DST dazu kommen.
mh schrieb:> solange es nur um Literale geht. Und ich finde ein Literal der Form> "2019-01-10"> lesbarer als die voherigen Alternativen. Vor allem wenn noch die> Uhrzeit, TZ und DST dazu kommen.
Ist halt auch schon wieder so ein konstruiertes Beispiel. Wieviele
literale Zeitangaben hattest du denn in den letzten 20.000 Zeilen Code,
die du geschrieben hast? Bei mir waren das (abgesehen von unit tests
vielleicht) ungefähr null ...
Sven B. schrieb:> Ist halt auch schon wieder so ein konstruiertes Beispiel. Wieviele> literale Zeitangaben hattest du denn in den letzten 20.000 Zeilen Code,> die du geschrieben hast? Bei mir waren das (abgesehen von unit tests> vielleicht) ungefähr null ...
Hab keine Lust die Zeilen zu zählen, aber grep sagt 27 im aktuellen
Projekt. Mal davon abgesehen sind unit tests genauso Quelltext der
geschrieben, gelesen und gewartet werden muss wie die eigentliche
Anwendung oder Bibliothek.
Vlad T. schrieb:> Aber wenn du das weißt, versteh ich deine Frage nicht.
Mmh, ich habe keine Frage gestellt.
Vlad T. schrieb:> Eine Funktion mit x Argumenten ist unnötig, du lässt den Typ die> Umrechenarbeit machen und nimmst nur das Resultat
Genau das hatte ich oben ja auch vorgeschlagen, eine Ausprägung davon
sind die einstelligen Funktionen im named-parameter-idiom.
Sven B. schrieb:> Eher würde ich mir Gedanken machen über> - evtl. sollten die Funktionsargumente in einem struct gruppiert sein?
Das kann man natürlich auch machen, wenn diese Typen dann einen
semantischen Zusammenhang haben:
1
structPoint{
2
intmax_tx;
3
intmax_ty;
4
};
5
6
voidfoo(constPoint&);
7
8
voidtest(){
9
foo({.x=100,.y=200});
10
}
Dafür kann man designated-initializer verwenden. Allerdings kann man die
Reihenfolge (in C++) nicht ändern.
Dirk K. schrieb:> Clean Code sagt: halte die Parameterliste minimal.
ja, genau: s.o. Hinweis auf einstellige Funktionen
mh schrieb:> Und ich finde ein Literal der Form> "2019-01-10"> lesbarer als die voherigen Alternativen.
Und welches Datum ist das jetzt?
Wilhelm M. schrieb:> mh schrieb:>> Und ich finde ein Literal der Form>> "2019-01-10">> lesbarer als die voherigen Alternativen.>> Und welches Datum ist das jetzt?
Ist das eine rhetorische Frage oder ist sie ernst gemeint?
mh schrieb:> Wilhelm M. schrieb:>> mh schrieb:>>> Und ich finde ein Literal der Form>>> "2019-01-10">>> lesbarer als die voherigen Alternativen.>>>> Und welches Datum ist das jetzt?>> Ist das eine rhetorische Frage oder ist sie ernst gemeint?
Also ich wüsste auch nicht ob das der 10. Januar oder der 1. Oktober
ist. Kann beides sein, je nach Notation.
Sven B. schrieb:> mh schrieb:>> Wilhelm M. schrieb:>>> mh schrieb:>>>> Und ich finde ein Literal der Form>>>> "2019-01-10">>>> lesbarer als die voherigen Alternativen.>>>>>> Und welches Datum ist das jetzt?>>>> Ist das eine rhetorische Frage oder ist sie ernst gemeint?>> Also ich wüsste auch nicht ob das der 10. Januar oder der 1. Oktober> ist. Kann beides sein, je nach Notation.
Wo wird denn die Notation YYYY-DD-MM verwendet?
Sven B. schrieb:> mh schrieb:>> Wilhelm M. schrieb:>>> mh schrieb:>>>> Und ich finde ein Literal der Form>>>> "2019-01-10">>>> lesbarer als die voherigen Alternativen.>>>>>> Und welches Datum ist das jetzt?>>>> Ist das eine rhetorische Frage oder ist sie ernst gemeint?>> Also ich wüsste auch nicht ob das der 10. Januar oder der 1. Oktober> ist. Kann beides sein, je nach Notation.
Es gibt laut der weltweiten Übersicht in Wikipedia (EN) nur eine
Variante, die mit dem Jahr beginnt und das ist die, bei er die letzte
Zahl nicht bei 12 endet, also YYYY-MM-DD.
Man muss C++ als das sehen was es ist: eine 40 Jahre alte
Programmiersprache. Heutzutage gibt es modernere Programmiersprachen,
die solche und andere Probleme nicht haben und viele Dinge eleganter
lösen.
Ja man kann in C++ viel mit template meta programming etc. erweitern,
aber vielleicht wäre es auch sinnvoll einfach mal nach vorn zu schauen.
;-)
... schrieb:> Heutzutage gibt es modernere Programmiersprachen,> die solche und andere Probleme nicht haben und viele Dinge eleganter> lösen.
Beispiele?
Wilhelm M. schrieb:> Beispiele?
Named Parameters in Verbindung mit starker statischer Typisierung
gibt es bspw. in Ada, C#, Ceylon, Fortran, Kotlin, Modula-3, Nemerle,
Nim, OCaml, Racket, Scala, Standard ML und VisualBasic.
Daneben haben auch einige dynamisch stark typisierte Sprachen named
Parameters.
Auch für C++ gab es im ISO-Komitee schon einen diesbezüglichen
Vorschlag, der aber wohl nicht angenommen wurde:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4172.htm
mh schrieb:> Ist das eine rhetorische Frage oder ist sie ernst gemeint?
Nein, sie ist ernst gemeint.
Zu schreiben
1
Dated{"2019-01-10"};
hat dieselbe Qualität wie mein Eingangbeispiel
1
Dated{1,2,3};
Der Leser des Codes, weiß nicht sofort, dass es im String um das Format
yyyy-mm-dd geht, oder bei den Argumenten des Beispiel-Konstruktors
Date(dd,mm,yy). Abgesehen davon steht dieses Beispiel natürlich
pars-pro-toto für alle Fälle, wo man mehrstellige Funktionen mit
unspezifischen Datentypen hat (s.a. der Hinweise auf Clean-Code). In dem
Datumsstring ist das genauso, dort hat man auch Ganzzahlen in einem
String, die für die Entitäten Jahr, Monat und Tag stehen, aber ohne das
es erkennbar ist bzw. erzwungen wird. Ein "2019y-01m-10d" hätte schon
geholfen. Allerdings würde hier eine falsche Darstellung erst zur
Laufzeit auffallen können (sofern man nicht einen consteval UDL-op hat).
Solche Fehler sollten aber schon zur Compilezeit möglichst vermieden
werden. Ein
1
autod=2019_year/1_month/10_day;
würde entweder zur Laufzeit korrekt sein oder nicht kompilieren.
Mir ging es doch hauptsächlich darum, dass man eine Schnittstelle so
gestalten sollte, dass sie eben leicht korrekt und nur schwer falsch zu
benutzen ist (als Zitat nicht von mir, sondern von Scott Meyers).
In diesem Zusammenhang könnte man auch sagen, dass es zwar die
eingebauten Datentypen gibt / geben muss, aber nur, damit man sie in
einer Schnittstelle bzw. im client-code nicht verwendet. Es fängt
schon mit so einfachen Dingen an, wie
1
module.distance(1.0);
Ist das jetzt 1.0cm oder 1.0km?
Besser wäre
1
usingnamespaceSI.literals;
2
module.distance(1.0_m);
Man verwendet also domänenspezifische Datentypen. Damit wird so etwas
dann auch richtig:
1
autod1=1_m;
2
autod2=1_km;
3
module.distance(d1+d2);
statt
1
doubled1=1.0;// Meter
2
doubled2=1.0;// Kilometer
3
module.distance(d1+d2);// ups
Wie man domänenspzifische Datentypen umsetzt, nur als Vokabulartypen
oder als algebraisches Typsystem (SI-Einheiten oder Zeit/Datum inklusive
deren Konvertierung mit Umrechnung), und welche Realisierung man wählt
(konkrete Klassen oder tagged-templates-types) hängt davon ab, wie viel
Aufwand der Bibliotheksersteller betreiben möchte.
Mehrstellige Funktionen sind natürlich noch kritischer. Manchmal lassen
sie sich aber nicht vermeiden. Dann aber sollte man die Schnittstelle
eben so sicher wie möglich gestalten. Was nicht bedeutet, dass der
Anwender keine Fehler mehr machen kann. Aber wenn man gleichzeitig die
Sicherheit (im Sinne von: es compiliert nicht. Denn nicht compilierende
Code ist zunächst mal sicher) etwas erhöhen und die Bequemlichkeit etwas
steigern kann (Permutationen der Argumente), sollte man es auch tun.
Entwickler wollen keine Doku lesen.
Genau dazu sind (in C++) domänenspezifische Datentypen und permutierte
Parameterlisten ein Weg. Der ist zwar schon seit Urzeiten bekannt,
jedoch m.E. erst seit dem C++11-Urknall wieder stärker im Fokus.
Wie in
Beitrag "Re: Typsichere und permutierte Parameterlisten in C++"
geschrieben, kann man auch domänenspezifische Datentypen (hier: Point)
als POD bzw. structural-type einsetzen, wenn sie keine Invarianten
haben, und dann designated-initializer verwenden, die dann wie benannte
Argumente wirken.
Es gibt aber auch Fälle, wo man die einzelnen Parameter einer
mehrstelligen Funktion nicht zu einem neuen Typ zusammenfassen möchte
(etwa weil es semantisch keinen Sinn macht). Und jetzt sind wir wieder
bei meinem Eingangsbeispiel. Um es in so einem Fall dem Anwender
möglichst einfach zu machen, kann man (man muss nicht) permutierte
Parameterlisten anbieten. Bei zwei Parametern ist das zu Fuß ok, bei
drei schon eine ganze Menge boilerplate seitens des Bibliothekschreibers
(nicht des Clienten). Dafür war diese Anregung gedacht (und ja, sie hat
zero-runtime-overhead).
... schrieb:> Heutzutage gibt es modernere Programmiersprachen,> die solche und andere Probleme nicht haben und viele Dinge eleganter> lösen.Wilhelm M. schrieb:> ... schrieb:>> Heutzutage gibt es modernere Programmiersprachen,>> die solche und andere Probleme nicht haben und viele Dinge eleganter>> lösen.>> Beispiele?Yalu X. schrieb:> Named Parameters in Verbindung mit starker statischer Typisierung> gibt es bspw. in Ada, C#, Ceylon, Fortran, Kotlin, Modula-3, Nemerle,> Nim, OCaml, Racket, Scala, Standard ML und VisualBasic.
Naja, named parameter bzw. name arguments gibt es in viel mehr Sprachen:
https://rosettacode.org/wiki/Named_parametershttps://en.wikipedia.org/wiki/Named_parameter
Allerdings lösen die meisten davon nicht das Problem, um das es hier
(teilweise) geht.
Denn sie belassen / verlagern die Verantwortung auf die Seite des
Aufrufers. Damit bleibt es aber nur ein nettes, optionales Feature.
Welche Sprache bietet die Möglichkeit, durch eine
Sigantur/Attribute/you_name_it den Aufrufer dazu zu zwingen, Argumente
mit Parameternamen zu versehen?
Yalu X. schrieb:> Auch für C++ gab es im ISO-Komitee schon einen diesbezüglichen> Vorschlag, der aber wohl nicht angenommen wurde:>> http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4172.htm
Ja. Wurde diskutiert und abgelehnt:
http://cplusplus.github.io/EWG/ewg-closed.html#150
Wilhelm M. schrieb:> In diesem Zusammenhang könnte man auch sagen, dass es zwar die> eingebauten Datentypen gibt / geben muss, aber nur, damit man sie in> einer Schnittstelle bzw. im client-code nicht verwendet. Es fängt> schon mit so einfachen Dingen an, wie> module.distance(1.0);>> Ist das jetzt 1.0cm oder 1.0km?
Die Einheitenliterale finde ich ganz cool. Andererseits tut es meiner
Meinung nach für den meisten Code auch eine simple Konvention, was die
Einheiten sind. Ich verwende meist projektweit ein passendes Präfix-Set
(z.B. µs, mm, GHz) und fertig, da geht auch quasi nie was schief.
Ich finde man muss hier ein bisschen pragmatisch sein und nur Probleme
lösen, die es auch gibt. Die Gefahr, die technische Komplexität des
Codes durch gut gemeinte "Vereinfachungen" bis zur Unwartbarkeit
aufzublasen, ist in C++ sehr groß.
Sven B. schrieb:> Die Einheitenliterale finde ich ganz cool. Andererseits tut es meiner> Meinung nach für den meisten Code auch eine simple Konvention, was die> Einheiten sind. Ich verwende meist projektweit ein passendes Präfix-Set> (z.B. µs, mm, GHz) und fertig, da geht auch quasi nie was schief.
Naja, nicht die user-defined-literal Ops sind hier der Schlüssel zum
Erfolg, sondern die domänenspezifischen Datentypen für physikal. Größen.
Also:
Wilhelm M. schrieb:> Der Leser des Codes, weiß nicht sofort, dass es im String um das Format> yyyy-mm-dd geht,
Sollte er aber. Du erwartest ja auch, dass der Leser bei
1
autod2=1_km;
von 1 Kilometer und nicht von 1 Kilomeile ausgeht.
Sven B. schrieb:> Ich finde man muss hier ein bisschen pragmatisch sein und nur Probleme> lösen, die es auch gibt. Die Gefahr, die technische Komplexität des> Codes durch gut gemeinte "Vereinfachungen" bis zur Unwartbarkeit> aufzublasen, ist in C++ sehr groß.
Da kann ich 100% zustimmen.
mh schrieb:> Sollte er aber. Du erwartest ja auch, dass der Leser beiauto d2 = 1_km;> von 1 Kilometer und nicht von 1 Kilomeile ausgeht.
Genau das steht ja da. Andernfalls wäre es
1
autod3=1_knm;
mit NM als internationales Kürzel für nautische Meile. Und wer mag führt
die Landmeile ein:
Wilhelm M. schrieb:> auto d3 = 1_knm;>> mit NM als internationales Kürzel für nautische Meile. Und wer mag führt> die Landmeile ein:
Dann aber 1_NM für nautische Meile, weil nm Nanometer ist.
Johann L. schrieb:> Wilhelm M. schrieb:>> auto d3 = 1_knm;>>>> mit NM als internationales Kürzel für nautische Meile. Und wer mag führt>> die Landmeile ein:>> Dann aber 1_NM für nautische Meile, weil nm Nanometer ist.
Das stimmt.
Im namespace SI::literals gibt / gäbe es nur _nm für Nanometer und kein
_NM nautische Meilen. Dazu würde man eine UDL-Op außerhalb des
Namensraumes der SI-Einheiten benötigen.
Wilhelm M. schrieb:> mh schrieb:>> Sollte er aber. Du erwartest ja auch, dass der Leser beiauto d2 = 1_km;>> von 1 Kilometer und nicht von 1 Kilomeile ausgeht.>> Genau das steht ja da. Andernfalls wäre es> auto d3 = 1_knm;>> mit NM als internationales Kürzel für nautische Meile.
Also für Einheiten sind internationale Konventionen in Ordnung, für das
Datum nicht?
> Und wer mag führt die Landmeile ein:> auto d4 = 1_lm;
lm ist das Einheitenzeichen für Lumen (eine SI Einheit).
Wilhelm M. schrieb:> mh schrieb:>> Also für Einheiten sind internationale Konventionen in Ordnung, für das>> Datum nicht?>> Natürlich.> Aber dann bitte> auto d = "2019-1-10"_iso8601
Sicher nicht. Zum einen enthält die Iso8601 mehr als nur diese Format
für das Datum, es müsste also wenn deutlich spezieller sein als
_iso8601. Zum anderen sehe ich es als überflüssig an. Es gibt keine
echte Verwechslungsgefahr, da es kein Format gibt, mit dem es
verwechselt werden könnte. Und es verhindert keinen der Fehler, die
meiner Erfahrung nach tatsächlich auftreten.
mh schrieb:> Wilhelm M. schrieb:>> mh schrieb:>>> Also für Einheiten sind internationale Konventionen in Ordnung, für das>>> Datum nicht?>>>> Natürlich.>> Aber dann bitte>> auto d = "2019-1-10"_iso8601>> Sicher nicht. Zum einen enthält die Iso8601 mehr als nur diese Format> für das Datum, es müsste also wenn deutlich spezieller sein als> _iso8601.
Der UDL-Op kann von mir aus alle Formate beherrschen, die ISO8601 zur
Verfügung stellt. Heraus kommt immer der Datentyp bspw. Date (ich
glaube, dass hast Du nocht nicht verstanden).
> Zum anderen sehe ich es als überflüssig an. Es gibt keine> echte Verwechslungsgefahr, da es kein Format gibt, mit dem es> verwechselt werden könnte. Und es verhindert keinen der Fehler, die> meiner Erfahrung nach tatsächlich auftreten.
Nur mal so als Beispiel:
Wilhelm M. schrieb:> Der UDL-Op kann von mir aus alle Formate beherrschen, die ISO8601 zur> Verfügung stellt. Heraus kommt immer der Datentyp bspw. Date (ich> glaube, dass hast Du nocht nicht verstanden).
Ich glaube du hast nicht verstanden, das 8601 noch mehr als das Datum
umfasst.
Wilhelm M. schrieb:> Ein C-String ist eine unspezifische Zeichenkette, kein Typ für ein> Datum.
Welchen Vorteil hat hier ein "echter" Typ gegenüber einem
String-Literal? Der Compiler kann, wie dir bewusst ist, auch das Literal
zur Compilezeit überprüfen. Ich brauche nur ein constexpr zu einer
Funktion hinzufügen, die so oder so existieren muss, weil ich mehrere
Daten zur Laufzeit aus einer Datei lese. Und rate mal in welchem Format
sie in der Datei stehen? Ich habe also genau eine Funktion, die ich
warten und testen muss. Du musst zusätzlich ein System aus Klassen- und
Funktionstemplates testen.
mh schrieb:> Wilhelm M. schrieb:>> Der UDL-Op kann von mir aus alle Formate beherrschen, die ISO8601 zur>> Verfügung stellt. Heraus kommt immer der Datentyp bspw. Date (ich>> glaube, dass hast Du nocht nicht verstanden).> Ich glaube du hast nicht verstanden, das 8601 noch mehr als das Datum> umfasst.
Du sprichst jetzt vollkommen wirr: was Du meinst/willst, habe ich doch
genauso oben geschrieben.
mh schrieb:> Welchen Vorteil hat hier ein "echter" Typ gegenüber einem> String-Literal?
Typsicherheit.
mh schrieb:> Ich habe also genau eine Funktion, die ich> warten und testen muss.
Ja, und ...?
mh schrieb:> Du musst zusätzlich ein System aus Klassen- und> Funktionstemplates testen.
Wie kommst Du denn darauf? Der UDL-Op ruft einfach Deine tolle Funktion
auf.
Ich glaube, Du hast Dich jetzt in der grundsätzlichen Ablehnung von UDT
und domänenspezifischen DT vollkommen verrannt. Du lebst anscheinend in
einer Welt, in der alles ein int ist, und falls nicht, dann ein
C-String.
Wilhelm M. schrieb:> mh schrieb:>> Wilhelm M. schrieb:>>> Der UDL-Op kann von mir aus alle Formate beherrschen, die ISO8601 zur>>> Verfügung stellt. Heraus kommt immer der Datentyp bspw. Date (ich>>> glaube, dass hast Du nocht nicht verstanden).>> Ich glaube du hast nicht verstanden, das 8601 noch mehr als das Datum>> umfasst.>> Du sprichst jetzt vollkommen wirr: was Du meinst/willst, habe ich doch> genauso oben geschrieben.>> mh schrieb:>> Welchen Vorteil hat hier ein "echter" Typ gegenüber einem>> String-Literal?>> Typsicherheit.
Wo ist das mehr Sicherheit gegenüber einen String Literal, der zur
Compiilezeit geprüft wird?
> mh schrieb:>> Ich habe also genau eine Funktion, die ich>> warten und testen muss.>> Ja, und ...?>> mh schrieb:>> Du musst zusätzlich ein System aus Klassen- und>> Funktionstemplates testen.>> Wie kommst Du denn darauf? Der UDL-Op ruft einfach Deine tolle Funktion> auf.
Ja die eine funktion musst du auch warten. Und deine ganze UDL existiert
magisch, ohne dass sie jemand warten und testen müsste?
> Ich glaube, Du hast Dich jetzt in der grundsätzlichen Ablehnung von UDT> und domänenspezifischen DT vollkommen verrannt. Du lebst anscheinend in> einer Welt, in der alles ein int ist, und falls nicht, dann ein> C-String.
Vielleicht hast du dich auch verrant? Ich benutze DT und UDL da, wo sie
echte Vorteile bringen. Wo sie keine echten Vorteile bringen sind sie
unnötige Komplexität und damit echte Nachteile.
mh schrieb:> Wo ist das mehr Sicherheit gegenüber einen String Literal, der zur> Compiilezeit geprüft wird?
Siehe Eingangsthema!
mh schrieb:> a die eine funktion musst du auch warten. Und deine ganze UDL existiert> magisch, ohne dass sie jemand warten und testen müsste?
Und Du rufst Deine Funktion nur von einer einzigen Stelle auf?
Kopfschüttel.
Wilhelm M. schrieb:> mh schrieb:>> Wo ist das mehr Sicherheit gegenüber einen String Literal, der zur>> Compiilezeit geprüft wird?>> Siehe Eingangsthema!
Ich sehe da kein Argument für oder gegen String Literale, die zur
Compilezeit überprüft werden.
> mh schrieb:>> a die eine funktion musst du auch warten. Und deine ganze UDL existiert>> magisch, ohne dass sie jemand warten und testen müsste?>> Und Du rufst Deine Funktion nur von einer einzigen Stelle auf?> Kopfschüttel.
Nein natürlich nicht. Aber was hat das mit der zusätzlichen Komplexität
der UDL zu tun?
Ich muss die eine Funktion testen. Dann kann sie überall genutzt werden.
Du musst zusätzlich die ganze UDL testen und kannst sie dann überall
benutzen (nachdem du aus allem ein Template gemacht hast, um die
Permutierbarkeit zu nutzen, mit all den Problemen die dabei entstehen).
Wilhelm M. schrieb:> Yalu X. schrieb:>> Named Parameters in Verbindung mit starker statischer Typisierung>> gibt es bspw. in Ada, C#, Ceylon, Fortran, Kotlin, Modula-3, Nemerle,>> Nim, OCaml, Racket, Scala, Standard ML und VisualBasic.>> Naja, named parameter bzw. name arguments gibt es in viel mehr Sprachen:
Da zeigt, dass es sich dabei um ein Feature handelt, dass von vielen als
sehr sinnvoll erachtet wird. Nur die C++-Programmierer müssen leider
darauf verzichten.
> Allerdings lösen die meisten davon nicht das Problem, um das es hier> (teilweise) geht.
Einen Teil davon schon.
Beispiel:
Wenn sich ein Programmierer bzgl. der Reihenfolge der Argumente nicht
ganz sicher ist und zu faul ist, in der Doku nachzuschlagen, wird er die
Parameternamen freiwillig verwenden, wodurch der Funktionsaufruf auf
Anhieb korrekt wird. Dieses Vorgehen ist gängige Praxis in Sprachen, die
named Parameters unterstützen, und erleichtert das Leben ganz gewaltig
bei der Nutzung vieler Grafikbibliotheken, wo jeder Funktionsaufruf
neben ein paar wenigen obligatorischen noch jede Menge optionale
Parameter hat.
Verwendet man stattdessen, deinem Vorschlag folgend, für jedes Argument
einen eigenen Wrapper-Typ, wird der lesefaule Programmierer nacheinander
verschiedene Permutationen der Argumente durchprobieren, bis er eine
findet, bei der der Compiler nicht meckert. Anstatt diese zweifelhafte
Vorgehensweise zu unterstützen, sollte man den Programmierer motivieren,
öfter in die Doku zu schauen, was zudem eine ganze Reihe weiterer
Probleme löst. Oder man setzt ihnen eine IDE vor, bei der Informationen
zu den einzelnen Funktionsargumenten während des Eintippens automatisch
aufpoppen.
Und wie ich oben schon schrieb: Solche Konstrukte bringen nur dann
etwas, wenn sie wenigsten halbwegs konsequent durchgezogen werden. In
der C++-Standardbibliothek findet man so gut wie keine Fälle, wo das
tatsächlich nach deinen Vortstellungen umgesetzt wird. Eine der wenigen
Ausnahmen ist std::chrono, wo das Ganze sogar bis zum Exzess getrieben
wird. Dabei stellt das Thema Datumsformate einen so winzigen Aspekt in
der gesamten C++-Programmierung, das ich mich frage, warum man
ausgerechnet dort so einen Zinnober macht.
Ganz abgesehen davon ist das versehentliche Vertauschen der Elemente
von Datumsangaben ein praktisch nicht existentes Problem, da der Fehler
normalerweise schon beim ersten Testlauf aufgedeckt wird. Oder hast
du schon einmal erlebt, dass es so ein Fehler unentdeckt bis zum
Endanwender geschafft hat?
Man kann in C++ viel machen, man kann aber auch viel übertreiben.
Yalu X. schrieb:> Einen Teil davon schon.Wilhelm M. schrieb:> Welche Sprache bietet die Möglichkeit, durch eine> Sigantur/Attribute/you_name_it den Aufrufer dazu zu zwingen, Argumente> mit Parameternamen zu versehen?
Wenn Du hierzu Sprachen nennen kannst, dann bin ich ganz bei Dir.
Wilhelm M. schrieb:> Yalu X. schrieb:>> Einen Teil davon schon.>> Wilhelm M. schrieb:>> Welche Sprache bietet die Möglichkeit, durch eine>> Sigantur/Attribute/you_name_it den Aufrufer dazu zu zwingen, Argumente>> mit Parameternamen zu versehen?>> Wenn Du hierzu Sprachen nennen kannst, dann bin ich ganz bei Dir.
Python, keine gute Idee, aber es geht.
Bzw. nach offiziellem Standard ohne die Underscores:
1
autot1=12s/3;
2
autot2=12d/3;
Die erste Zeile dürfte ohne weitere Erklärung den meisten klar sein: Ein
Drittel von 12 Sekunden sind 4 Sekunden.
Aber was ist ein Drittel von 12 Tagen?
Etwa 4 Tage?
Weit gefehlt: Das Ergebnis ist der 12. März :D
An die Vergewaltigung der Shift-Operatoren für I/O-Zwecke hat man sich
ja inzwischen halbwegs gewöhnt. Dass nun den Divisionsoperator dasselbe
Schicksal ereilt, ist für mich so, als ob die armseligen ISO-C++-Würmer¹
der Königin der Wissenschaften direkt ins Gesicht spucken würden.
Mal im Ernst: Warum können sich die Mitglieder des C++Komitees nicht auf
die Beseitigung der wirklichen Probleme von C++ konzentrieren, sondern
müssen sich stattdessen einen solchen Unfug aus den Fingern saugen? Als
ob die Sprache und ihre Standardbibliothek nicht schon ohne diese
Auswüchse fett genug wäre.
——————————————
¹) Das ist nicht als Beleidigung gedacht, sondern soll nur meinem Unmut
etwas mehr Ausdruck verleihen.
Yalu X. schrieb:> Mal im Ernst: Warum können sich die Mitglieder des C++Komitees nicht auf> die Beseitigung der wirklichen Probleme von C++ konzentrieren, sondern> müssen sich stattdessen einen solchen Unfug aus den Fingern saugen? Als> ob die Sprache und ihre Standardbibliothek nicht schon ohne diese> Auswüchse fett genug wäre.
Sie schaffen es nach 30 Jahren immerhin auch pi in den Standard
aufzunehmen ...
Yalu X. schrieb:> Ein C++-Programm könnte dann bspw. folgende beiden Zeilen enthalten:>> auto t1 = 12_s / 3;> auto t2 = 12_d / 3;>> Bzw. nach offiziellem Standard ohne die Underscores:>> auto t1 = 12s / 3;> auto t2 = 12d / 3;
In welcher Version hast Du das denn zum Laufen gebracht?
Wilhelm M. schrieb:> Yalu X. schrieb:>> Ein C++-Programm könnte dann bspw. folgende beiden Zeilen enthalten:>>>> auto t1 = 12_s / 3;>> auto t2 = 12_d / 3;>>>> Bzw. nach offiziellem Standard ohne die Underscores:>>>> auto t1 = 12s / 3;>> auto t2 = 12d / 3;>> In welcher Version hast Du das denn zum Laufen gebracht?
Ah, jezt habe ich das auch in der Doku gelesen. Laufen tut es bei mir im
gcc-10 nicht.
Also, wenn das so ist, dann finde ich das auch großen Mist!
Wilhelm M. schrieb:> mh schrieb:>> Python, keine gute Idee, aber es geht.>> Kind-of. So wie ich das verstehe, wird daraus eine map.
In python wirds ein dictionary.
Wilhelm M. schrieb:> Wilhelm M. schrieb:>> Yalu X. schrieb:>>> Ein C++-Programm könnte dann bspw. folgende beiden Zeilen enthalten:>>>>>> auto t1 = 12_s / 3;>>> auto t2 = 12_d / 3;>>>>>> Bzw. nach offiziellem Standard ohne die Underscores:>>>>>> auto t1 = 12s / 3;>>> auto t2 = 12d / 3;>>>> In welcher Version hast Du das denn zum Laufen gebracht?>> Ah, jezt habe ich das auch in der Doku gelesen. Laufen tut es bei mir im> gcc-10 nicht.>> Also, wenn das so ist, dann finde ich das auch großen Mist!
Ich bekomme es bei clang grad auch nicht zum laufen, aber so wie ich
cppreference lese, wird es für c++20 funktionieren.
mh schrieb:> Wilhelm M. schrieb:>> mh schrieb:>>> Python, keine gute Idee, aber es geht.>>>> Kind-of. So wie ich das verstehe, wird daraus eine map.>> In python wirds ein dictionary.
Also Laufzeit-Check / Fehler.
da müsste man genauer zwischen Zeit (absolut) und Zeitspanne
unterscheiden. Aber wie soll ein 12_d dann sagen ob damit der 12. Tag
oder 12 Tage gemeint sind?
Johannes S. schrieb:> da müsste man genauer zwischen Zeit (absolut) und Zeitspanne> unterscheiden. Aber wie soll ein 12_d dann sagen ob damit der 12. Tag> oder 12 Tage gemeint sind?
Es ist der /-op.
Der wird einmal das Divisions-Op für Dauern angewendet, und einmal als
Builder für ein Zeitpunkt. DAs ist zwar klar dokumentiert. Es ist aber
diametral zu dem, was ich in diesem Beitrag mir wünsche. Deswegen ist
das - wenn es denn so kommen sollte, die cppref-Seite ist von 2018 - für
großen Mist.
Yalu X. schrieb:> Wenn sich ein Programmierer bzgl. der Reihenfolge der Argumente nicht> ganz sicher ist und zu faul ist, in der Doku nachzuschlagen, wird er die> Parameternamen freiwillig verwenden, wodurch der Funktionsaufruf auf> Anhieb korrekt wird. Dieses Vorgehen ist gängige Praxis in Sprachen, die> named Parameters unterstützen, und erleichtert das Leben ganz gewaltig> bei der Nutzung vieler Grafikbibliotheken, wo jeder Funktionsaufruf> neben ein paar wenigen obligatorischen noch jede Menge optionale> Parameter hat.
Also wenn ich die Wahl hab zwischen
- RTFM und
- RTFM + Compiler-Fehler
dann nehm ich mit absoluter Sicherheit #2.
Yalu X. schrieb:> Und wie ich oben schon schrieb: Solche Konstrukte bringen nur dann> etwas, wenn sie wenigsten halbwegs konsequent durchgezogen werden. In> der C++-Standardbibliothek findet man so gut wie keine Fälle, wo das> tatsächlich nach deinen Vortstellungen umgesetzt wird. Eine der wenigen> Ausnahmen ist std::chrono, wo das Ganze sogar bis zum Exzess getrieben> wird. Dabei stellt das Thema Datumsformate einen so winzigen Aspekt in> der gesamten C++-Programmierung, das ich mich frage, warum man> ausgerechnet dort so einen Zinnober macht.
Und nur weil ein Teil des Codes etwas nicht nutzt darf es ein anderer
auch nicht? Schreibst du auch C++98 nur weil irgendeine genutzte
Bibliothek das tut?
Johannes S. schrieb:> da müsste man genauer zwischen Zeit (absolut) und Zeitspanne> unterscheiden. Aber wie soll ein 12_d dann sagen ob damit der 12. Tag> oder 12 Tage gemeint sind?
Müsste man. Aber dann wird ein _d nicht mehr reichen und die ganze
"chrono-Sprache" wird noch komplexer. Davon abgesehen wird aus 1d ein
std::chrono::day, eine Spezialisierung von std::chrono::duration.
Vincent H. schrieb:> Also wenn ich die Wahl hab zwischen> - RTFM und> - RTFM + Compiler-Fehler> dann nehm ich mit absoluter Sicherheit #2.
Ich würde bevorzugen
- RTFM + es funktioniert wie beschrieben
Oder ist das "-" als Negierung zu verstehen?
mh schrieb:> Vincent H. schrieb:>> Also wenn ich die Wahl hab zwischen>> - RTFM und>> - RTFM + Compiler-Fehler>> dann nehm ich mit absoluter Sicherheit #2.>> Ich würde bevorzugen> - RTFM + es funktioniert wie beschrieben> Oder ist das "-" als Negierung zu verstehen?
Ich meinte nur typensichere Parameter und nicht irgendwelche Tupeln.
So wie ich Yalu X. posts deute zieht er ein
Vincent H. schrieb:> mh schrieb:>> Vincent H. schrieb:>>> Also wenn ich die Wahl hab zwischen>>> - RTFM und>>> - RTFM + Compiler-Fehler>>> dann nehm ich mit absoluter Sicherheit #2.>>>> Ich würde bevorzugen>> - RTFM + es funktioniert wie beschrieben>> Oder ist das "-" als Negierung zu verstehen?>> Ich meinte nur typensichere Parameter und nicht irgendwelche Tupeln.
Ah, verstehe.
> So wie ich Yalu X. posts deute zieht er einvoid foo(int, int, int)>> einemvoid foo(A, B, C)>> vor.
So wie ich ihn verstanden habe, ist das eine zu stark verkürzte
Zusammenfassung seiner Position.
Ich will es gar nicht schön reden. Jedoch werden hier die beiden
literal-namespaces gemischt, wodurch das UDL-Problem in Kombination mit
op=/ als Builder entsteht.
Wilhelm M. schrieb:> mh schrieb:>> std::chrono::day, eine Spezialisierung von std::chrono::duration>> Das hast Du mit std::chrono::days verwechselt.>> Auszug aus date.h:> // Detailed interface> // day> class day {
Jepp hab ich. Und je länger ich mir std::chrono angucke, um so
verwirrter werde ich.
operator""d/m/y liefern ne duration operator""s/y ne eigene Klasse
zurück und was ist mit dem Operator für month?
Der operator/ ist mehr als fragwürdig.
Aus https://en.cppreference.com/w/cpp/chrono/operator""d:
1
Access to this operator can be gained with using namespace std::literals, using namespace std::chrono_literals, and using namespace std::literals::chrono_literals.
Drei namespaces? Da konnte man sich wohl nicht entscheiden.
Ich hatte vor einiger Zeit mal große Hoffnungen für std::chrono, aber
wie immer bei der Standardbibliothek, werden sie enttäscht. Ich hoffe
sie verstümmeln fmtlib nicht auch noch.
Vincent H. schrieb:> So wie ich Yalu X. posts deute zieht er ein>> void foo(int, int, int)>> einem>> void foo(A, B, C)>> vor.
In den meisten Fällen, ja. Ausnahme: Wenn die Typen A, B und C sowieso
schon anderweitig vorhanden sind und genutzt werden.
Wie würdest du (die Frage geht auch an Wilhelm) x/y-Koordinaten als
Funktionsargumente implementieren?
Der klassische Weg anhand eines Beispiels:
Das Ganze könnte optional noch getemplated werden, um anstelle von int
auch andere Typen wie bspw. double verwenden zu können. Aber lassen wir
diese Möglichkeit erst einmal außen vor.
Würdet ihr für x und y zwei verschiedene Wrapper-Datentypen definieren?
Vorteile:
- Ein versehentliches Vertauschen dieser Argumente wird damit
verhindert. Dies ist deshalb von Bedeutung, weil bei reinen
Textdisplays die Funktionssignatur oft so
aussieht, was einer Vertauschung von x und y gleichkommt.
- x und y sind gerichtete Längen, die in verschiedene Richtungen zeigen.
Deswegen ergibt bspw. die Addition x+y wenig Sinn und könnte mit den
Typ-Wrappern ebenfalls verhindert werden.
Nachteile:
- Es sind zusätzliche Symbole für die Wrapper-Typen erforderlich, die
Namensraum belegen. Bei nur zwei Typen wie hier ist das sicher kein
Problem, wenn man aber alle Argumente von allen Funktionen wrappt,
kann das schon etwas unübersichtlich werden.
- Wenn x und y ausschließlich für den Funktionsaufruf gewrappt werden:
- Störendes Rauschen im Quellcode durch die Typnamen und die Klammern:
1
drawText(X_Coord{x},Y_Coord{y},"Hello");
- Der o.g. Vorteil, nämlich die Addition x+y zu verhindern,
entfällt.
- Werden die Variablen x und y von vornherein als X_Coord bzw. Y_Coord
deklariert, sieht der Funktionsaufruf zwar schöner aus, und die
Addition x+y wird verhindert, dafür muss aber für X_Coord und Y_Coord
ein ganzer Satz von Arithmetik- und I/O-Funktionen definiert werden,
um bspw. x1+x2, y/x und std::cout<<x schreiben zu können.
Für mich überwiegen die Nachteile, weswegen ich lieber bei der
klassischen Methode bleibe. Das bedeutet natürlich, dass ich mich erst
über Typen und Reihenfolge der Funktionsargumente informieren muss. Aber
auch wenn ich dazu zu faul bin, werde ich spätestens beim ersten Test
erkennen, dass der Text an die falsche Displayposition gesetzt wird, und
kann den Fehler sofort beheben.
Yalu X. schrieb:> - Der o.g. Vorteil, nämlich die Addition x+y zu verhindern,> entfällt.
Was aber auch ein gewaltiger Nachteil bzw. dann Vorteil sein kann, wenn
man Drehungen oder sowas realisieren will. Offensichtliche Nachteile von
der Schwemme an Datentypen ist die Einbuße an Benutzbarkeit. Zeitweise
hat fast jede c++ library ihre eigenen shared- oder unique-pointer
implementiert. Wenn ich mir vorstelle, immer die X-Koordinaten
(un-)wrappen zu müssen, um die von einer lib in die andere zu bekommen,
wird mir ganz anders:
Ich habe da so das Bild vor Augen, eine Koordinate 10 mal in den
spezifischen X-Koordinaten-Typ wrappen und wieder unwrappen zu müssen.
Dann kommt irgendwann die große Gegenbewegung:
Uniform Data-Types! Yeah! Float!!!
Yalu X. schrieb:> Würdet ihr für x und y zwei verschiedene Wrapper-Datentypen definieren?
Ja (bzw. jein)
Allerdings würde ich die genannte Schnittstelle auf
beschränken.
Ggf. hat Point2D auch einen ctor in Polarkoordinaten mit Radius/Winkel
und entsprechenden DT, dann wäre auch entsprechende Typen für
kartesische Koordinaten sicher sinnvoll. Auch - wie Du schon bemerkt
hast - um sinnlose Operationen der primitiven DT für Koordinaten
auszuschließen.
Allerdings würde ich - wie Du Dir sicher denken kannst - bei der Wahl
der Typen noch wesentlich weiter gehen. Nein, nicht würde, sondern wir
machen das in ähnlich gelagerten Fällen tatsächlich. In der Konsequenz
ist das dann der std::chrono oder der evtl. kommenden std::units
qualitativ nicht unähnlich.
mh schrieb:> Wilhelm M. schrieb:>> mh schrieb:>>> std::chrono::day, eine Spezialisierung von std::chrono::duration>>>> Das hast Du mit std::chrono::days verwechselt.>>>> Auszug aus date.h:>> // Detailed interface>> // day>> class day {>> Jepp hab ich. Und je länger ich mir std::chrono angucke, um so> verwirrter werde ich.>> operator""d/m/y liefern ne duration operator""s/y ne eigene Klasse> zurück und was ist mit dem Operator für month?>> Der operator/ ist mehr als fragwürdig.>
Das kann man durchaus so sehen und deshalb ist
"2020-01-16"_iso8601
Die gar keine so schlechte Idee. Es gibt den ""-Operator in
verschiedenen Ausführungen, eine bekommt einen String übergeben. Dieser
kann mit Hilfe von (immer häufiger vorhandenen) constexpr Funktionen in
eine Instanz eines passenden Objekts überführt werden. Hier wäre das
eine Datums-Struktur/Klasse, deren korrekte weitere Verwendung der
vollen Typprüfung unterliegt.
Man kann z.B. UDLs 1_sec, 1_msec, 1000_Hz, usw. In einen Typ wandeln,
der die Anzahl Clockticks (also in F_CPU Einheiten) darstellt und so
z.B. einen Timer passend einstellen, wobei in Source-Code steht was man
beabsichtigt (Timer-Int alle 1ms) und im "Time-Frequency.hpp"-Include
die technische Realisierung steht. Das hat man früher mit Makros
gemacht, aber fehlersicher ist was anderes. Ich möchte z.B. auch nicht
mehr auf Sourcecode-Debugger verzichten, obwohl ich das vor 35Jahren
auch nicht hatte und es somit wohl auch ohne geht.
Zum /-Operator in chrono vermute ich, daß man das mit den erst später
gekommenem ""operator(const char*) anders gelöst hätte. Ohne die
Angelsachsen vielleicht auch.
Carl D. schrieb:> Das kann man durchaus so sehen und deshalb ist> "2020-01-16"_iso8601> Die gar keine so schlechte Idee. Es gibt den ""-Operator in> verschiedenen Ausführungen, eine bekommt einen String übergeben. Dieser> kann mit Hilfe von (immer häufiger vorhandenen) constexpr Funktionen in> eine Instanz eines passenden Objekts überführt werden.
Das wurde ein paar Posts weiter oben schon mit "gemischtem Ergebnis"
diskutiert. Aber vielleicht hast du ja eine zufriedenstellende Antwort
auf die Frage nach diesem "passenden Objekt".
Carl D. schrieb:> Zum /-Operator in chrono vermute ich, daß man das mit den erst später> gekommenem ""operator(const char*) anders gelöst hätte. Ohne die> Angelsachsen vielleicht auch.
Der operator/ in chrono kommt erst mit C++20 ...
Wilhelm M. schrieb:> Ggf. hat Point2D auch einen ctor in Polarkoordinaten mit Radius/Winkel> und entsprechenden DT, dann wäre auch entsprechende Typen für> kartesische Koordinaten sicher sinnvoll.
Da klingt für mich ein Denkfehler an: Der Datentyp besagt nichts über
eine Bedeutung. Ein Radius zB ist eine Länge, genau wie die kartesischen
Koordinaten. Es macht durchaus Sinn, r Längeneinheiten in eine
Richtung zu verschieben. Wenn man "korrekt" vorgehen will ist die Länge
ein dimensionsloser Skalar (als Faktor des Basisvektores des
betrachteten Raumes). Ein Term "x+y"=x*v0+y*v1 ist keineswegs Unsinn,
sondern zB eine komplexe Zahl. Wenn v1=i und v0=1 kann man dann nochmal
mit v1 multiplizieren und landet wieder bei v0 oder bei einer
stinknormalen Zahl.
Denk mal drüber nach: Es ist einfach immer falsch.
mh schrieb:> Aber vielleicht hast du ja eine zufriedenstellende Antwort> auf die Frage nach diesem "passenden Objekt".
Naja, eine Klasse, die einzig eine unsigned Variable beinhaltet, die
beim AVR bis zu 32x10^6 Ticks aufnehmen kann, also z.B. uint32_t. Da ich
alles constexpr verwende macht die Größe dem Zielsystem kein Problem,
denn es kommen am Ende ja Werte raus, die in die 8-/16-Bit Timer
Register passen, oder eben static_asserts, wenn die Zielharware den
gewünschten Wert nicht hergibt.
Man kann dabei auch gleich noch einen passenden Teilerwert (constexpr)
ausrechnen lassen, sodaß auch dieser Teil versteckt ist und
unterschiedlich Teilerwerte verschiedener Timer (0/1 vs. 2) im
"Applikations"-Code nicht auftauchen. Ein
1
Timer0timer;
kann also durch
1
Timer2timer;
getauscht werden, ohne an der Initialisierung auf 10_msec zu ändern. Und
notfalls könnten man per static_assert auf exakte Darstellbarkeit des
gewünschten Intervalls prüfen.
Konkret zur Frage (nur auf dem Pad runtergetippt und ohne Garantie für
Compilierbarkeit):
1
structTicks{
2
uint32_tm_value;
3
constexprTicks(constuint32_t):m_value(v){};
4
5
// ... diverse nützliche Operatoren, ...
6
7
};
8
9
Ticksoperator""_Hz(unsignedlonglonginti){
10
returnTicks(F_CPU/i);
11
}
12
13
Ticksoperator""_kHz(unsignedlonglonginti){
14
returnTicks(F_CPU/i/1000;
15
}
16
...
17
timer.init(10_Hz);
18
...
Hinter dem Timer steckt natürlich auch noch die ein oder andere Zeile
"bösen" (template) C++-Code, den ich jetzt nicht parat habe. Aber den
mußte ich ja auch nur einmal schreiben.
Ob sich das gelohnt hat für meine gelegentlichen Hobbyexperimente mit
AVR/STM32? Zeitlich sicher nicht, aber gelernt hab ich dabei einiges.
Selbst wenn ich beruflich mit C++ nicht in Kontakt komme, hat sich auch
dort mein Programmierstil verändert. Viele der C++>=11-Konzepte hat man
nämlich auch in ganz andere Sprachen übernommen. wenn auch manchmal gut
versteckt.
Carl D. schrieb:> Das kann man durchaus so sehen und deshalb ist> "2020-01-16"_iso8601> Die gar keine so schlechte Idee. Es gibt den ""-Operator in> verschiedenen Ausführungen, eine bekommt einen String übergeben. Dieser> kann mit Hilfe von (immer häufiger vorhandenen) constexpr Funktionen in> eine Instanz eines passenden Objekts überführt werden. Hier wäre das> eine Datums-Struktur/Klasse, deren korrekte weitere Verwendung der> vollen Typprüfung unterliegt.
Oh man, davon spreche ich seit Anfang dieses Post.
Carl D. schrieb:> Man kann z.B. UDLs 1_sec, 1_msec, 1000_Hz, usw. In einen Typ wandeln,> der die Anzahl Clockticks (also in F_CPU Einheiten) darstellt
Warum willst Du die Frequenz-Angabe in Ticks hier umwandeln. Es ist
besser und wesentlich sinnvoller, wenn bei dem UDL-OP _Hz auch eine
Frequenz heraus kommt!
Carl D. schrieb:> Ticks operator""_Hz(unsigned long long int i){> return Ticks(F_CPU/i);> }
Halte ich für falsch (s.o.), es sollte eine Frequenz heraus kommen.
Wilhelm M. schrieb:> Carl D. schrieb:>> Ticks operator""_Hz(unsigned long long int i){>> return Ticks(F_CPU/i);>> }>> Halte ich für falsch (s.o.), es sollte eine Frequenz heraus kommen.
warum mach ich diese einfache Variante?
Weil ich das für µC's verwende und einfach nur den Zusammenhang zwischen
Frequenz/Zeit und CPU-Clocks abstrahieren will. Ich will in Einheiten,
die mir geläufig sind, (meist) Konstanten festlegen, die in den
Einheiten der HW (hier Clock-Ticks) im FLASH stehen.
Zudem ist das ein einfaches Beispiel, das auch nicht-C++-Begeisterte
(guten Willen vorausgesetzt) nachvollziehen können. <chrono> ist dazu
da, auch Fans (wie mich), eher abzuschrecken. Und wenn jemand
Abneigungen gegen Operatoren entwickelt, die abhängig von den Operanden
entweder dividieren (12s / 3; // 12/3 sec) oder addieren (12d / 3; Day12
of Month3), dann kann ich das verstehen. Das widerspricht der
"Scott-Meyers-Regel".
Carl D. schrieb:> Und wenn jemand> Abneigungen gegen Operatoren entwickelt, die abhängig von den Operanden> entweder dividieren (12s / 3; // 12/3 sec) oder addieren (12d / 3; Day12> of Month3), dann kann ich das verstehen. Das widerspricht der> "Scott-Meyers-Regel".
Und was ist mit Operatoren, die behaupten ne Frequenz zurück zu geben,
es aber nicht tun?
Carl D. schrieb:> Abneigungen gegen Operatoren entwickelt, die abhängig von den Operanden> entweder dividieren (12s / 3; // 12/3 sec) oder addieren (12d / 3; Day12> of Month3), dann kann ich das verstehen. Das widerspricht der> "Scott-Meyers-Regel".
Je nun, für Amerikaner ist die Datumsschreibweise 2020/01/18 gewohnt und
üblich. Was können die dafür, daß ein unbedeutender Rest der Welt das
anders macht, und für die das '/' an der Stelle eine völlig
unverständliche Überraschung darstellt.
Oliver
Oliver S. schrieb:> Je nun, für Amerikaner ist die Datumsschreibweise 2020/01/18 gewohnt und> üblich.
sollten sich Programmierer auch angewöhnen, so läßt sich das ordentlich
sortieren.
Nur bei ihren imperialen Einheiten sollten sich Amerikaner mal etwas
bewegen...
Haben die numeric literals eigentlich nur einen divider? Können die auch
°C/Fahrenheit automagisch umrechnen?
Oliver S. schrieb:> Je nun, für Amerikaner ist die Datumsschreibweise 2020/01/18 gewohnt und> üblich.
Eben nicht. Es gibt dort folgende numerische Schreibweisen:
Am Schluss hat man um 3 Zahlen zu addieren 300 Zeilen Metacode. Das
endet immer total unlesbar und hat am Schluss mehr Fehler als die
pragmatische Lösung :/
Naja, ganz kleines Beispiel.
Wenn ich mir bspw. manche total simplen Fehler hier im Forum anschaue,
die etwa mit Lesen von Daten aus dem Flash zu tun haben bei AVR. Die
proprietäre GCC-Erweiterung __flash gibt es ja nur im GCC-C-Mode, nicht
in GCC-C++-Mode. Also muss man in C++ PROGMEM benutzen. Und wenn es dann
ein Array-of-C-Strings ist, dann fangen die Probleme an.
Egal ob AVR oder nicht, bei mir gibt es überhaupt keine Möglichkeit,
(const char*) irgendwie bspw. auszugeben. Falls man also versucht, einen
C-String (aus dem flash oder nicht) zu verwenden, compiliert das
Programm nicht.
Stattdessen gibt es den gen. Typ AVR::Pgm::String<>, und der kann nur
durch "Hallo"_pgm erstellt werden.
Daher reicht
Wilhelm M. schrieb:> Wenn ich mir bspw. manche total simplen Fehler hier im Forum anschaue,> die etwa mit Lesen von Daten aus dem Flash zu tun haben bei AVR. Die> proprietäre GCC-Erweiterung __flash gibt es ja nur im GCC-C-Mode, nicht> in GCC-C++-Mode. Also muss man in C++ PROGMEM benutzen. Und wenn es dann> ein Array-of-C-Strings ist, dann fangen die Probleme an.
Und du glaubst, dass jemand der Probleme mit PROGMEM hat, problemlos mit
templates, namespaces und user-defined literals klar kommt?
mh schrieb:> Und du glaubst, dass jemand der Probleme mit PROGMEM hat, problemlos mit> templates, namespaces und user-defined literals klar kommt?
Absolut. Wenn er sie nur benutzt: ja.
In dem obigen Beispiel kannst Du den unnamed-namespace ja auch
weglassen. Dann deutet gar nichts mehr auf templates und namespaces hin.
Das ""_pgm kann man genauso leicht wie PROGMEM oder __flash erklären.
Wilhelm M. schrieb:> mh schrieb:>> Und du glaubst, dass jemand der Probleme mit PROGMEM hat, problemlos mit>> templates, namespaces und user-defined literals klar kommt?>> Absolut. Wenn er sie nur benutzt: ja.>> In dem obigen Beispiel kannst Du den unnamed-namespace ja auch> weglassen. Dann deutet gar nichts mehr auf templates und namespaces hin.> Das ""_pgm kann man genauso leicht wie PROGMEM oder __flash erklären.
Ich sehe nicht warum es einfacher sein soll. Wie schreibt man denn eine
Funktion, die einen ""_pgm entgegen nimmt? Wie verändert man einen
""_pgm? Wie komme ich an einen Teilstring des ""_pgm? Kann ich einen
""_pgm zur Laufzeit in den Flash schreiben? Ich bezweifle ernsthaft,
dass eine der Fragen wirklich einfacher zu erklären ist als für PROGMEM.
für die Verwendung mit Einheiten sind die numerischen Literale schon
ganz nett, habe damit ein bisschen im onlinegdb gespielt. Aber warum
liefert 1s/10 keine plausiblen Werte?
1
#include<stdio.h>
2
#include<chrono>
3
#include<thread>
4
#include<iostream>
5
6
usingnamespacestd;
7
8
intmain()
9
{
10
autostart=std::chrono::system_clock::now();// This and "end"'s type is std::chrono::time_point
Johannes S. schrieb:> das ist auch ok, genau wie 1.0s / 10. Aber was ist da anders? Oder ist> es das Gleiche Problem wie bei den Tagen / x?
Du teilst einmal 1s ganzzahlig durch 10 --> 0s.
Bei 1s / 10.0 wird die Sekundendarstellung zunächst auf double gebracht
und dann durch 10.0 dividiert. Eigentlich ganz normal.
Wilhelm M. schrieb:> Johannes S. schrieb:>> das ist auch ok, genau wie 1.0s / 10. Aber was ist da anders? Oder ist>> es das Gleiche Problem wie bei den Tagen / x?>> Du teilst einmal 1s ganzzahlig durch 10 --> 0s.>> Bei 1s / 10.0 wird die Sekundendarstellung zunächst auf double gebracht> und dann durch 10.0 dividiert. Eigentlich ganz normal.
Nur macht man die ganze Sache mit der UDL, damit man einen Zeittyp hat
und keinen int. Eine Zeit hat kein Ganzahlverhalten. Wenn man eine
Sekunde durch 10 teilt, bekommt man 1/10s oder 100ms.
mh schrieb:> Wenn man eine> Sekunde durch 10 teilt, bekommt man 1/10s oder 100ms.
so war mein Gedanke. Und auch wenn ich ein eigenes sleep mit
Millisekunden mache wird zu 0 dividiert. Und das mag dann auch keine
1.0s.
das mySleep kann nicht mit 1.0s aufgerufen werden:
1
mySleep(1.0s);
1
main.cpp: In function ‘int main()’:
2
main.cpp:30:17: error: could not convert ‘std::literals::chrono_literals::operator""s(1.0e+0l)’ from ‘std::chrono::duration’ to ‘std::chrono::milliseconds {aka std::chrono::duration >}’
3
mySleep(1.0s);
4
^~~~
Ich hatte überlegt sowas zu machen weil das sleep_for für mein OS ein
uint32_t in Millisekunden haben möchte, die <threads> sind nicht
implementiert. Diese Tests hier sind wie gesagt erstmal mit dem online
compiler.
Eigentlich wollte ich ein sleep das ich mit beliebigen Zeiten aufrufen
kann. Die Systemfunktion erwartet aber ms, das muss sich ja irgendwie
automatisch umrechnen lassen. Das habe ich mit dem duration_cast
gefunden. Sieht wild aus, funktioniert aber alles:
Ich muss zwar immer nachschlagen wenn ich sowas defineren will, aber ich
kann mich trotzdem mit den UDLs und den Möglichkeiten anfreunden. Der µC
schluckt es auch und es wird nicht mehr Code erzeugt als über den Aufruf
mit einfachen Konstanten.
Ob der Link funktioniert weiß ich nicht, habe mich da angemeldet:
https://www.onlinegdb.com/edit/H1MQkBJb8
sorry wenn OT, aber mit den UDL muss man ja erstmal ein bisschen üben.
Johannes S. schrieb:> und es wird nicht mehr Code erzeugt als über den Aufruf> mit einfachen Konstanten.
Natürlich, dass ist eines der Mantren von C++.
Wilhelm M. schrieb:> Natürlich, dass ist eines der Mantren von C++.
muss man ausprobiert haben das man es glaubt :)
Wilhelm M. schrieb:> Wird doch automatisch konvertiert.
das klappte nicht wimre.
Johannes S. schrieb:> das klappte nicht wimre.
Bei mir schon, bis auf Rep == double eben.
Bei Deiner Funktion wird aber intern auf milliseconds gewandelt. Ich
weiß nicht, ob es das ist, was man erwartet, wenn man 1.0s / 10e4
übergibt.
Sprich: 0ms
main.cpp:51:18: error: could not convert ‘std::literals::chrono_literals::operator""us<'1', '0', '0', '0'>()’ from ‘std::chrono::microseconds {aka std::chrono::duration >}’ to ‘std::chrono::milliseconds {aka std::chrono::duration >}’
3
mySleep2(1000us);
4
^~~~~~
5
main.cpp:52:18: error: could not convert ‘std::literals::chrono_literals::operator""us<'1', '0', '0'>()’ from ‘std::chrono::microseconds {aka std::chrono::duration >}’ to ‘std::chrono::milliseconds {aka std::chrono::duration >}’
gut, µs will er nicht in ms konvertieren. Aber dann muss das im sleep
umgerechnet werden weil das System ms erwartet. Meine Variante (bzw. was
auch das sleep_for() in <thread> macht) erlaubt alles, bei Zeiten < 0 ms
wird nicht gewartet.
Aber um das Detail ging es ja nicht, eher wie man die automatische
Umrechnung nutzen kann. Was bei dem sleep schon praktisch ist.
Johannes S. schrieb:> Meine Variante (bzw. was> auch das sleep_for() in <thread> macht) erlaubt alles, bei Zeiten < 0 ms> wird nicht gewartet.
Du meinst < 1ms.
Dann würde ich eben auf milliseconds als parametertyp wählen, alles
andere ist eine unklare Schnittstelle. Und dann ist es eben auch gut,
dass 1.0s /1e4 nicht geht.
Wilhelm M. schrieb:> Schau Dir mein BEispiel genau(!) an.>> Und es ist doch genau richt, dass es bei Dir nicht compiliert. So soll> es sein.
Das ist vielleicht das, was du möchtest. Einige Punkte, die mich daran
stören:
- milliseconds ist ganzzahlig.
- Es gibt nur bei Konstanten einen Fehler
- Der Fehler ist "kann nicht konvertiert werden" und nicht "Wartzeit >=
1 ms benötigt"
- 1000µs kann nicht in 1ms konvertiert werden
wenn die Systemfunktion hier nur ganzahligen ms wartet ist das sicher
richtig auch nur ms oder vielfache zu aktzeptieren, finde ich auch
überzeugend.
Wenn es als Konstante reinkommt kann man auf float und µs auch
verzichten, das suggeriert ja nur das eine Funktion es dann auch kann.
Insofern wieder die Typsicherheit die C++ bietet.
Für andere Fälle die auch wirklich FP verarbeitet hat man trotzdem alle
Freiheiten wenn man mal ein Angstrom pro Lichtjahr oder sowas braucht.
Wilhelm M. schrieb:> Date d{Day{1], Month{2}, Century{20}.year{3}}; // (1)
Zählt man da von 1 oder von 0?
Jaja, das 1. Jahrhundert läuft natürlich von 0 bis 99.
Imho ist so etwas nichts weiter als ein Hack, um - vermeindlichen oder
realen - Unzulänglichkeiten der Sprache auszuweichen, der zu
unglaublichem Code-Bloat und stark eingeschränkter Benutzbarkeit führt.
Ob benannte Parameter nun sinnvoll sind oder nicht: Das ist eine
Vergewaltigung des Typsystems, die in ihrer Pseudo-Sicherheit meint den
Stein der Weisen gefunden zu haben und als Designentscheidung jedem das
Leben unnötig schwer macht, der eine solche API benutzen muss. Gerade
so, als ob man nicht doch in die Dokumentation schauen müsste.
Eine Silver-Bullet per Excellence.
mh schrieb:> Wilhelm M. schrieb:>> Schau Dir mein BEispiel genau(!) an.>>>> Und es ist doch genau richt, dass es bei Dir nicht compiliert. So soll>> es sein.>> Das ist vielleicht das, was du möchtest.
Absolut: fragwüdige Konstrukte sollten nicht compilieren. Natürlich kann
ein Anwender sich darüber hinweg setzen. Dafür braucht er dann extra
Argumente und etwas extra Code, um das klar sichtbar zu machen.
> Einige Punkte, die mich daran> stören:> - milliseconds ist ganzzahlig.
In irgendeiner Einheit musst Du repräsentieren. Wenn Dir ms nicht
feingranular genug ist, dann nimm halt µs odet ns.
> - Es gibt nur bei Konstanten einen Fehler
Was meinst Du damit?
> - Der Fehler ist "kann nicht konvertiert werden" und nicht "Wartzeit >=> 1 ms benötigt"
Kannste ja als Laufzeit-Assertion einbauen.
Aber das Typsystem schützt Dich eben effektiv vor groben Unfung.
> - 1000µs kann nicht in 1ms konvertiert werden
Jedenfalls nicht implizit, genau. Keine impliziten, ändernden
Konversionen. Das ein double implizit zu einem int konvertiert werden
kann, hat man seit langem bereut (C-Erbe).
M.E. in Summe sehr erstrebenswerte Eigenschaften.
Wilhelm M. schrieb:> In irgendeiner Einheit musst Du repräsentieren.
Es geht nicht um die Einheit.
> Wenn Dir ms nicht feingranular genug ist, dann nimm halt µs odet ns.
Bei mir kommen häufiger Zeiten wie pi * 1s oder ln(10) * 1s vor. Ich
müsste also immer prüfen, welcher der duration Typen die beste Auflösung
bietet ohne einen Überlauf zu erzeugen, oder ich nutze ne
Fließpunktvariante. Wenn ich nicht überall den gleichen Typ benutze, ist
mein Quelltext übersät mit expliziten Konvertierungen. Es gibt für mich
gegenüber einem einfachen struct Time {double t} keinen Vorteil. Dafür
gibt es einen Haufen Nachteile. Es gibt jetzt zum Beispiel wieder einen
Haufen Kram im std namespace, den man umschiffen muss. Nen user-defined
literal ""_s ist keine so tolle Idee, wenn es schon zwei
unterschiedliche ""s im Standard gibt...
Wilhelm M. schrieb:>> - Es gibt nur bei Konstanten einen Fehler>> Was meinst Du damit?
Das war tatsächlich etwas zu stark verkürzt. Wenn man die Zeit nicht als
Compilezeit-Konstante hat, sondern sie als Nutzereingabe bekommt oder
berechnen muss, muss man im Allgemeinen konvertieren, runden und das
Ergebnis prüfen. Da man eh prüfen muss, bekommt man keinen wirklichen
Vorteil mehr aus dem Typensystem. Wenn die Rechnung zur Laufzeit 100µs
ergibt und man konvertiert die Zeit in Millisekunden, kann der Compiler
nicht warnen.
Wilhelm M. schrieb:> Kannste ja als Laufzeit-Assertion einbauen.> Aber das Typsystem schützt Dich eben effektiv vor groben Unfung.
Wie gesagt, die Tests braucht man in vielen Fällen, sobald man nicht nur
Konstanten hat. Das Typensystem schützt in diesem Fall gegen Fehler, die
in meinem Umfeld so gut wie nie auftreten und es hilft nicht gegen die
Fehler, die in meinem Umfeld tatsächlich häufiger vorkommen. Wie wir
gesehen haben, hilft es nicht vor 1h/10 == 0ms Wartezeit. Es schützt
einen auch nicht vor einer Wartezeit in negativen Millisekunden.
Wilhelm M. schrieb:> > - 1000µs kann nicht in 1ms konvertiert werden> Jedenfalls nicht implizit, genau. Keine impliziten, ändernden> Konversionen. Das ein double implizit zu einem int konvertiert werden> kann, hat man seit langem bereut (C-Erbe).
Ja, implizite Konversionen sind böse. Da sind wir wenigstens einer
Meinung. Das Problem beibt allerdings und ist eine Folge der vielen
unnötigen Typen. Es sollte keinen Unterschied machen, ob ich 1000µs oder
1ms schreibe.
mh schrieb:> Bei mir kommen häufiger Zeiten wie pi * 1s oder ln(10) * 1s vor. Ich> müsste also immer prüfen, welcher der duration Typen die beste Auflösung> bietet ohne einen Überlauf zu erzeugen, oder ich nutze ne> Fließpunktvariante.
Brauchst Du doch gar nicht: ein 1s * acos(-1.0) ergibt
std::chrono::duration<double, ratio<1,1>>. Ist also genau das, was Du
möchtest: es wird automatisch auf den Fließkommatyp erweitert. Es gelten
hier die gleichen Regeln wie bei den eingebauten DT. Mehr kannst Du Dir
eigentlich nicht wünschen.
mh schrieb:> Das Typensystem schützt in diesem Fall gegen Fehler, die> in meinem Umfeld so gut wie nie auftreten und es hilft nicht gegen die> Fehler, die in meinem Umfeld tatsächlich häufiger vorkommen. Wie wir> gesehen haben, hilft es nicht vor 1h/10 == 0ms Wartezeit.
Wenn Du mit der Modellierung eine Größe in ganzzahligen Vielfachen einer
Basisgröße nicht zufrieden bist, dann mach es eben anders, z.B. immer
mit double und ohne einer Basisgröße. Die C++-stdlib geht ganz bewusst
einen anderen Weg, z.B. um den Anwender die Freiheit zu geben,
ganzzahlig zu rechnen. Natürlich muss man sich dann über
Quantisierungseffekte Gedanken machen. Aber DAS sollte jedem, der sich
mit numerischer Mathematik auch im Rahmen einer Einführungsveranstaltung
beschäftigt hat, klar sein.
mh schrieb:> Da man eh prüfen muss, bekommt man keinen wirklichen> Vorteil mehr aus dem Typensystem.
Du vermischt ständig Werte und Typen.
mh schrieb:> Es sollte keinen Unterschied machen, ob ich 1000µs oder> 1ms schreibe.
s.o.
Wilhelm M. schrieb:> mh schrieb:>> Bei mir kommen häufiger Zeiten wie pi * 1s oder ln(10) * 1s vor. Ich>> müsste also immer prüfen, welcher der duration Typen die beste Auflösung>> bietet ohne einen Überlauf zu erzeugen, oder ich nutze ne>> Fließpunktvariante.>> Brauchst Du doch gar nicht: ein 1s * acos(-1.0) ergibt> std::chrono::duration<double, ratio<1,1>>. Ist also genau das, was Du> möchtest: es wird automatisch auf den Fließkommatyp erweitert. Es gelten> hier die gleichen Regeln wie bei den eingebauten DT. Mehr kannst Du Dir> eigentlich nicht wünschen.
Deswegen habe ich ja geschrieben "oder ich nutze ne Fließpunktvariante".
Sobald ich allerdings bei diesem Typ angekommen bin, hab ich keinen
Vorteil mehr von den anderen Typen, wenn ich keine expliziete
Konvertierungsorgie möchte.
Übrigens TypC = TypA * TypB ist nicht gerade wie bei den eingebauten DT.
Da ist es entweder TypA = TypA * TypB oder TypB = TypA * TypB.
> mh schrieb:>> Das Typensystem schützt in diesem Fall gegen Fehler, die>> in meinem Umfeld so gut wie nie auftreten und es hilft nicht gegen die>> Fehler, die in meinem Umfeld tatsächlich häufiger vorkommen. Wie wir>> gesehen haben, hilft es nicht vor 1h/10 == 0ms Wartezeit.>> Wenn Du mit der Modellierung eine Größe in ganzzahligen Vielfachen einer> Basisgröße nicht zufrieden bist, dann mach es eben anders, z.B. immer> mit double und ohne einer Basisgröße. Die C++-stdlib geht ganz bewusst> einen anderen Weg, z.B. um den Anwender die Freiheit zu geben,> ganzzahlig zu rechnen. Natürlich muss man sich dann über> Quantisierungseffekte Gedanken machen. Aber DAS sollte jedem, der sich> mit numerischer Mathematik auch im Rahmen einer Einführungsveranstaltung> beschäftigt hat, klar sein.
Also jeder, der erwartet, dass das Ergebnis von 1 Sekunde geteilt durch
10 gleich 0.1 Sekunden oder 100 Millisekunden ist, ist zu dumm oder faul
für dich? Ich erinnere dich mal an dein Lieblingszitat: "Make interfaces
easy to use correctly and hard to use incorrectly."
> mh schrieb:>> Da man eh prüfen muss, bekommt man keinen wirklichen>> Vorteil mehr aus dem Typensystem.>> Du vermischt ständig Werte und Typen.
Nein, tue ich nicht.
> mh schrieb:>> Es sollte keinen Unterschied machen, ob ich 1000µs oder>> 1ms schreibe.>> s.o.
Was soll ich oben sehen? Gibt es ein logisches Argument, warum der Wert
1000µs ungleich dem Wert 1ms sein soll, außer weil es mit std::chrono
eben nicht geht und inkompatibel mit deinen Vorstellung von einem
Typensystem ist? Aber das passiert, wenn man Werte und Typen mischt.