Forum: PC-Programmierung Typsichere und permutierte Parameterlisten in C++


von Wilhelm M. (wimalopaan)


Angehängte Dateien:

Lesenswert?

Mehrstellige Funktionen mit unspezifischen Parametertypen zu verwenden, 
ist nie eine gute Idee, wie etwa in:
1
Date d{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
Date d{Day{1], Month{2}, Century{20}.year{3}}; // (1)
2
3
Date d{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
int main() {
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
    constexpr Date d1{Year{1903}, Day{1}, Month{3}}; // every permutation possible
9
    constexpr Date d2{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_t simple(TT... vv) { // at least two parameters of type A and type B are neccessary
3
        auto a = std::get<A>(std::tuple{vv...});
4
        auto b = std::get<B>(std::tuple{vv...});
5
        return a.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:
1
    template<typename... TT>
2
    uint8_t foo(TT... vv) requires(is<List<TT...>, A, B, C>) {
3
        auto a = get<A>(vv...);
4
        auto b = get<B>(vv...);
5
        auto c = get<C>(vv...);
6
        return a + b + c;
7
    }

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.

: Verschoben durch Moderator
von mh (Gast)


Lesenswert?

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
1
Date d{Year{2019}, Month{1}, Day{10}}
kann man immer noch
1
Date d{Year{2019}, Month{10}, Day{1}}
schreiben.



* Das Fehlen von benannten Parametern:
1
Date d{year=2020, month=1, day=14}
2
std::copy(quelleStart=q.begin(), quelleEnde=q.end(), zielStart=z.begin())

von Yalu X. (yalu) (Moderator)


Lesenswert?

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.

von Vlad T. (vlad_tepesch)


Lesenswert?

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

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Vlad T. schrieb:
> gibt es in den neueren c++ standards nicht eigene literale für
> Zeiteinheiten?

Ja, in der chrono Lib.

von Wilhelm M. (wimalopaan)


Lesenswert?

Vlad T. schrieb:
> gleiches Prinzip natürlich mit Daten
> void setDate(Dateval v1);

Geht so, nur schöner ;-)

von Wilhelm M. (wimalopaan)


Lesenswert?

Warum ist eigentlich der Beitrag hierher verschoben worden? War doch 
unter Code-Schnipsel nicht schlecht aufgehoben, oder?

von Wilhelm M. (wimalopaan)


Lesenswert?

Vlad T. schrieb:
> gibt es in den neueren c++ standards nicht eigene literale für
> Zeiteinheiten?

FYI:

https://www.youtube.com/watch?v=adSAN282YIw&feature=emb_title

von mh (Gast)


Lesenswert?

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
1
Date d{"2019-01-10"}

von Wilhelm M. (wimalopaan)


Lesenswert?

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

von Vincent H. (vinci)


Lesenswert?

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
1
using Acceleration = wrapper<int32_t, struct AccelerationTag>;

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

von Vlad T. (vlad_tepesch)


Lesenswert?

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

von Sven B. (scummos)


Lesenswert?

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?

: Bearbeitet durch User
von Dirk K. (merciless)


Lesenswert?

Clean Code sagt: halte die Parameterliste minimal.
Ich sehe hier keine Probleme und wilde Konstrukte,
die schlechte Architektur unterstützen sollen,
sind mir suspekt.

Lesenswertes dazu:
https://www.matheus.ro/2018/01/29/clean-code-avoid-many-arguments-functions/
https://de.wikipedia.org/wiki/Single-Responsibility-Prinzip
https://refactoring.guru/introduce-parameter-object

merciless

von mh (Gast)


Lesenswert?

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.

von kghgdhuh (Gast)


Lesenswert?

User defined literals vllt.?

von Sven B. (scummos)


Lesenswert?

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

von mh (Gast)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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
struct Point {
2
    intmax_t x;
3
    intmax_t y;
4
};
5
6
void foo(const Point&);
7
8
void test() {
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?

von mh (Gast)


Lesenswert?

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?

von Sven B. (scummos)


Lesenswert?

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.

von mh (Gast)


Lesenswert?

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?

von Carl D. (jcw2)


Lesenswert?

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.

von ... (Gast)


Lesenswert?

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

Beitrag #6107599 wurde vom Autor gelöscht.
von Wilhelm M. (wimalopaan)


Lesenswert?

... schrieb:
> Heutzutage gibt es modernere Programmiersprachen,
> die solche und andere Probleme nicht haben und viele Dinge eleganter
> lösen.

Beispiele?

von Yalu X. (yalu) (Moderator)


Lesenswert?

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

von Wilhelm M. (wimalopaan)


Lesenswert?

mh schrieb:
> Ist das eine rhetorische Frage oder ist sie ernst gemeint?

Nein, sie ist ernst gemeint.

Zu schreiben
1
Date d{"2019-01-10"};

hat dieselbe Qualität wie mein Eingangbeispiel
1
Date d{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
auto d = 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
using namespace SI.literals;
2
module.distance(1.0_m);

Man verwendet also domänenspezifische Datentypen. Damit wird so etwas 
dann auch richtig:
1
auto d1 = 1_m;
2
auto d2 = 1_km;
3
module.distance(d1 + d2);

statt
1
double d1 = 1.0; // Meter
2
double d2 = 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.
1
auto s = 1_km;
2
auto t = 3_min;
3
auto v = s / t;
4
static_assert(std::is_same_v<decltype(v), SI::velocity);

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_parameters

https://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

von Sven B. (scummos)


Lesenswert?

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

von Wilhelm M. (wimalopaan)


Lesenswert?

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:
1
units::time::second_t a;
2
units::time::minute_t b(1.0);
3
4
a = b;
5
6
assert(a == 60_s)

oder
1
auto v = meter_t(100.0) / second_t(2.0);
2
3
static_assert(std::is_same_v<decltype(v), meters_per_second_t>);

und unsinnige Verknüpfungen comipilieren einfach nicht:
1
v += 1_m; // NOK

von mh (Gast)


Lesenswert?

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
auto d2 = 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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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
auto d3 = 1_knm;

mit NM als internationales Kürzel für nautische Meile. Und wer mag führt 
die Landmeile ein:
1
auto d4 = 1_lm;
2
3
auto d5 = d4 + d5 + d3;

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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.

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von mh (Gast)


Lesenswert?

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

von Wilhelm M. (wimalopaan)


Lesenswert?

mh schrieb:
> Also für Einheiten sind internationale Konventionen in Ordnung, für das
> Datum nicht?

Natürlich.
Aber dann bitte
1
auto d = "2019-1-10"_iso8601

von mh (Gast)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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:
1
auto d1 = "2019-1-10"; 
2
auto d2 = "2019-1-10"_iso8601;
3
4
static_assert(std::is_same_v<decltype(d1), const char*);
5
static_assert(std::is_same_v<decltype(d2), Date);
6
7
foo(d1); // ok
8
foo("bla"); // mmh?
9
10
bar(d2); // ok
11
bar("bla"); // not_ok

Ein C-String ist eine unspezifische Zeichenkette, kein Typ für ein 
Datum.

von mh (Gast)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von mh (Gast)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von mh (Gast)


Lesenswert?

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

von Yalu X. (yalu) (Moderator)


Lesenswert?

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.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Wilhelm M. schrieb:
> mit NM als internationales Kürzel für nautische Meile. Und wer mag führt
> die Landmeile ein:
>
> auto d4 = 1_lm;

lm ist Lumen

https://de.wikipedia.org/wiki/Lumen_(Einheit)

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von mh (Gast)


Lesenswert?

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.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Wilhelm M. schrieb:
> https://howardhinnant.github.io/date/date.html

Ich dachte erst, das sei ein Aprilscherz, ähnlich dem Goto für Python:

  http://entrian.com/goto/

Aber nein, diese seltsam anmutenden Zeit- und Datumsfunktionen sind
tatsächlich ernst gemeint und werden als std::chrono sogar Bestandteil
von C++20.

Das Ganze wird ganz besonders lustig, wenn man noch diesen Vorschlag

  http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1935r0.html

mit hinzunimmt.

Ein C++-Programm könnte dann bspw. folgende beiden Zeilen enthalten:

1
  auto t1 = 12_s / 3;
2
  auto t2 = 12_d / 3;

Bzw. nach offiziellem Standard ohne die Underscores:

1
  auto t1 = 12s / 3;
2
  auto t2 = 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.

von mh (Gast)


Lesenswert?

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

von Wilhelm M. (wimalopaan)


Lesenswert?

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?

von Wilhelm M. (wimalopaan)


Lesenswert?

mh schrieb:
> Python, keine gute Idee, aber es geht.

Kind-of. So wie ich das verstehe, wird daraus eine map.

von Wilhelm M. (wimalopaan)


Lesenswert?

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!

von mh (Gast)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von Johannes S. (Gast)


Lesenswert?

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?

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von Vincent H. (vinci)


Lesenswert?

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?

von mh (Gast)


Lesenswert?

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.

von mh (Gast)


Lesenswert?

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?

von Vincent H. (vinci)


Lesenswert?

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
1
void foo(int, int, int)

einem
1
void foo(A, B, C)

vor.

von Oliver S. (oliverso)


Lesenswert?

Wilhelm M. schrieb:
> Deswegen ist
> das - wenn es denn so kommen sollte, die cppref-Seite ist von 2018 - für
> großen Mist.
1
Document Number:N4842
2
Date:2019-11-27
3
Working Draft, Standard for ProgrammingLanguage C++
4
5
...
6
7
27.8.18  Conventional syntax operators [time.cal.operators]
8
A set of overloaded operator/functions provides a conventional syntax for the creation of civil calendardates.
9
10
[Note: The year, month, and day are accepted in any of the following 3 orders:
11
year/month/day
12
month/day/year
13
day/month/year

Immerhin ist damit das Datums-Reihenfolge-Problem vollumfänglich 
erschlagen ;)

Oliver

: Bearbeitet durch User
von mh (Gast)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

mh schrieb:
> std::chrono::day, eine Spezialisierung von std::chrono::duration

Das hast Du mit std::chrono::days verwechselt.

Auszug aus date.h:
1
   // Detailed interface
2
    // day
3
    class day {

Beitrag #6109483 wurde vom Autor gelöscht.
von Wilhelm M. (wimalopaan)


Lesenswert?

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.
1
#include "date.h"
2
#include <chrono>
3
4
using namespace date::literals; 
5
using namespace std::literals; 
6
7
template<typename C, typename R>
8
void foo(std::chrono::duration<C,R>) {}
9
void foo(date::month_day) {}
10
11
int main() {
12
    auto t1 = 12s;   
13
    auto t2 = 12_d / 3;
14
    foo(t1); // calls correct overload
15
    foo(t2); // calls correct overload
16
}

: Bearbeitet durch User
von mh (Gast)


Lesenswert?

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.

von Yalu X. (yalu) (Moderator)


Lesenswert?

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:

1
void drawText(int x, int y, const std::string &text);

und/oder

1
void drawText(const Point2D &p, const std::string &text);

wobei der Konstruktor von Point2D so aussieht:

1
Point2D(int x, int y);

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
1
  void drawText(int zeile, int spalte, const std::string &text);
  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.

von Heiko L. (zer0)


Lesenswert?

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!!!

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

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
1
void drawText(const Point2D &p, const std::string &text);

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.

von Carl D. (jcw2)


Lesenswert?

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.

von mh (Gast)


Lesenswert?

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

von Heiko L. (zer0)


Lesenswert?

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.

von Carl D. (jcw2)


Lesenswert?

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
 Timer0 timer;
 kann also durch
1
Timer2 timer;
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
struct Ticks {
2
uint32_t m_value;
3
constexpr Ticks(const uint32_t) : m_value(v){};
4
5
// ... diverse nützliche Operatoren, ...
6
7
};
8
9
Ticks operator""_Hz(unsigned long long int i){
10
  return Ticks(F_CPU/i);
11
}
12
13
Ticks operator""_kHz(unsigned long long int i){
14
  return Ticks(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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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!
1
template<typename Representation, typename Divider = ratio<1,1>> 
2
struct frequency;
3
        
4
using hertz = frequency<uint32_t, ratio<1, 1>>;
5
using megahertz = frequency<uint8_t, ratio<1, 1000000>>;
6
7
constexpr hertz operator""_Hz(unsigned long long int f){
8
  return hertz(f);
9
}
10
constexpr megahertz operator""_MHz(unsigned long long int f){
11
  return megahertz(f);
12
}

Deinen Timer kannst Du damit immer noch korrekt initialisieren, denn der 
sollte seine Taktquelle kennen.
1
template<typename Component, typename Clock, typename MCU = DefaultMcuType>
2
struct Timer {
3
    static void frequency(const hertz&);
4
};

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von Carl D. (jcw2)


Lesenswert?

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

von mh (Gast)


Lesenswert?

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?

von Oliver S. (oliverso)


Lesenswert?

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

von Johannes S. (Gast)


Lesenswert?

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?

von Yalu X. (yalu) (Moderator)


Lesenswert?

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:

1
1/18/20      m/d/yy
2
01/18/2020   mm/dd/yyyy
3
2020-01-18   yyyy-mm-dd

Die letzte ist auch die internationale Schreibweise und die einzige von
den dreien, die bei zeichenweiser Sortierung das erwartete Ergebnis
liefert.

https://en.wikipedia.org/wiki/Date_and_time_notation_in_the_United_States

: Bearbeitet durch Moderator
von Sven B. (scummos)


Lesenswert?

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 :/

von Wilhelm M. (wimalopaan)


Lesenswert?

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
1
namespace {
2
    std::tuple strings{"abc"_pgm, "def"_pgm, "abc"_pgm};
3
}

und alles ist gut.

von mh (Gast)


Lesenswert?

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?

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

: Bearbeitet durch User
von mh (Gast)


Lesenswert?

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.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Wilhelm M. schrieb:
> Die proprietäre GCC-Erweiterung __flash

proprietär???

von Wilhelm M. (wimalopaan)


Lesenswert?

Johann L. schrieb:
> Wilhelm M. schrieb:
>> Die proprietäre GCC-Erweiterung __flash
>
> proprietär???

Stimmt: schlecht Wortwahl - sechs, setzen ;-)

von Johannes S. (Gast)


Lesenswert?

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
using namespace std;
7
8
int main()
9
{
10
    auto start = std::chrono::system_clock::now(); // This and "end"'s type is std::chrono::time_point
11
    { // The code to test
12
//        std::this_thread::sleep_for(200ms);       // ok
13
//        std::this_thread::sleep_for(100ms * 2);   // ok
14
//        std::this_thread::sleep_for(2s);          // ok
15
//        std::this_thread::sleep_for(2s / 10);     // ok
16
        std::this_thread::sleep_for(1s / 10);     // not ok
17
    }
18
    auto end = std::chrono::system_clock::now();
19
20
    std::chrono::duration<double> elapsed = end - start;
21
    std::cout << "Elapsed time: " << elapsed.count() << "s";
22
23
    return 0;
24
}
1
Elapsed time: 6.38e-07s

mal unabhängig davon das der onlinegdb ja nicht beliebige Zeitscheiben 
hat, für die ok Zeiten kommen plausible Ausgaben, auch für 1.0s / 10.

von Wilhelm M. (wimalopaan)


Lesenswert?

Probier mal 1s/ 10.0;

von Johannes S. (Gast)


Lesenswert?

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?

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von mh (Gast)


Lesenswert?

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.

von Johannes S. (Gast)


Lesenswert?

ja, richtig. Man muss sich an solchen Luxus erst gewöhnen und das der 
Compiler gar nicht zaubert :)

von Johannes S. (Gast)


Lesenswert?

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.
1
void mySleep(std::chrono::milliseconds millisec)
2
{
3
    std::this_thread::sleep_for(millisec);
4
}

von Wilhelm M. (wimalopaan)


Lesenswert?

Johannes S. schrieb:
> Und das mag dann auch keine
> 1.0s.

Wie meinst Du das?
Oder rufst Du das etwa mit 1s/10 auf?

: Bearbeitet durch User
von Johannes S. (Gast)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

Johannes S. schrieb:
> das mySleep kann nicht mit 1.0s aufgerufen werden:

Aber mit 1s

von Johannes S. (Gast)


Lesenswert?

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:
1
/******************************************************************************
2
3
Welcome to GDB Online.
4
GDB online is an online compiler and debugger tool for C, C++, Python, Java, PHP, Ruby, Perl,
5
C#, VB, Swift, Pascal, Fortran, Haskell, Objective-C, Assembly, HTML, CSS, JS, SQLite, Prolog.
6
Code, Compile, Run and Debug online from anywhere in world.
7
8
*******************************************************************************/
9
#include <stdio.h>
10
#include <chrono>
11
#include <thread>
12
#include <iostream>
13
14
using namespace std;
15
16
constexpr std::chrono::duration<long double, std::milli> operator ""_nanocenturies(long double val)
17
{
18
    return std::chrono::duration<long double, std::ratio<3155,1000>>(val);
19
}
20
21
22
template< class Rep, class Period >
23
void mySleep(const std::chrono::duration<Rep, Period>& sleeptime)
24
{
25
    uint32_t sleeptime_ms = std::chrono::duration_cast<std::chrono::milliseconds>(sleeptime).count();
26
    
27
    //ThisThread::sleep_for(sleeptime_ms);
28
    printf("sleeptime_ms: %u\n", sleeptime_ms);
29
}
30
31
int main()
32
{
33
    auto start = std::chrono::system_clock::now(); // This and "end"'s type is std::chrono::time_point
34
    { 
35
        mySleep(1.0s);
36
        mySleep(1s);
37
        mySleep(100ms);
38
        mySleep(1000us);
39
        mySleep(100us);             // to short, 0 ms
40
        mySleep(10.0_nanocenturies);
41
    }
42
    auto end = std::chrono::system_clock::now();
43
44
    std::chrono::duration<double> elapsed = end - start;
45
    std::cout << "Elapsed time: " << elapsed.count() << "s";
46
47
    return 0;
48
}

Ausgabe:
1
sleeptime_ms: 1000
2
sleeptime_ms: 1000
3
sleeptime_ms: 100
4
sleeptime_ms: 1
5
sleeptime_ms: 0
6
sleeptime_ms: 31550
7
Elapsed time: 0.000497093s

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

Wenn Du nicht auf das Fließkommaformat bestehst, reicht doch:
1
void mySleep(std::chrono::nanoseconds t) {
2
    std::cout << "ns: " << t.count() << '\n';
3
}

Wird doch automatisch konvertiert.

: Bearbeitet durch User
von Johannes S. (Gast)


Lesenswert?

hatte ich ja vorher, aber mit FP ist schöner. Die Konstanten werden ja 
auch vom Compiler zu Integer umgerechnet, kosten also keine Laufzeit.

von Wilhelm M. (wimalopaan)


Lesenswert?

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

von Johannes S. (Gast)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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

von Johannes S. (Gast)


Lesenswert?

1
void mySleep2(std::chrono::milliseconds t) {
2
    std::cout << "mySleep2 ms: " << t.count() << '\n';
3
}
4
5
        //mySleep2(1.0s);
6
        //mySleep2(0.1s);
7
        mySleep2(1s);
8
        mySleep2(100ms);
9
        mySleep2(1000us);
10
        mySleep2(100us);             // to short, 0 ms
11
        //mySleep2(10.0_nanocenturies);
1
main.cpp: In function ‘int main()’:
2
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 >}’
6
         mySleep2(100us);             // to short, 0 ms
7
                  ^~~~~

von Wilhelm M. (wimalopaan)


Lesenswert?

Schau Dir mein BEispiel genau(!) an.

Und es ist doch genau richt, dass es bei Dir nicht compiliert. So soll 
es sein.

: Bearbeitet durch User
von Johannes S. (Gast)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von mh (Gast)


Lesenswert?

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

von Johannes S. (Gast)


Lesenswert?

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.

von Heiko L. (zer0)


Lesenswert?

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.

: Bearbeitet durch User
von Heiko L. (zer0)


Lesenswert?

Wenn man "Typsicherheit" noch nicht einmal auf Ebene der Sprachkonzepte 
hinkriegt!

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von mh (Gast)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

: Bearbeitet durch User
von mh (Gast)


Lesenswert?

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.

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.