C++Guns – RoboBlog blogging the bot

27.02.2018

C++ Guns: Templated class specialization where template argument is a template

Filed under: Allgemein — Tags: — Thomas @ 16:02

Okay das ist mit Abstand das krasseste was ich seit langem mit Templates gemacht habe, obwohl die Aufgabe doch recht klar und einfach ist. Der Code ist von der Syntax her auch noch irgendwo okay. Naja, vllt. auch nicht.

Ich habe meine Experimentelle Graph Klasse mit entsprechenden Iteratoren. Datencontainer und Algorithmus trennen und mit Iteratoren wieder verbinden klappt btw. ziemlich gut. Da hat er 1984 was richtig gemacht.

Seit C++17 gibt es std::distance(first, last) welche die Anzahl der Elemente zwischen first und last zurück gibt. Die möchte ich nutzen. Dazu müssen meine Iteratoren std kompatibel werden. Genauer gesagt müssen die Anforderungen an einem InputIterator erfüllt werden. Hierfür muss man std::iterator_traits spezialisieren. Und die 5 Variablen difference_type, value_type, pointer, reference, iterator_category erstellen und auch hoffentlich richtig belegen.

Funktionen im std namespace zu überladen, oder template zu spezialisieren ist in der Regel nicht erlaubt, aber bei std::iterator_trais explizit gewünscht. Man muss nur darauf achten den Code nicht in irgendeinem Namespace zu schreiben, sonst wird er vom Compiler nicht gefunden.

#include <iterator>

template<>
struct std::iterator_traits<MyIterator> {
    using Iterator          = ...;
    using difference_type   = ...;
    using value_type        = ...;
    using pointer           = ...;
    using reference         = ...;
    using iterator_category = ...;
};

So weit so einfach, dumm nur, wenn die Klasse MyIterator wiederrum eine Template Klasse ist. Also braucht man eine Template Klassen Spezialisierung mit einer Template Klasse. Okay das ist schon halb WTF. Wir wollen also ein Template spezialisieren, so dass die Template Parameter gesetzt sind, aber die Parameter sind selbst wieder Template. Die Spezialisierung ist also garnicht speziell, nur ein bisschen.

Wenn man am Ende noch an das Keyword typename denkt (der Compiler erinnert einem gut daran), da wir ja alles vertemplated haben, schaut es am Ende so aus:

template<typename Graph>
struct std::iterator_traits<cpl::graph::ConnNodeIterator<Graph>> {
    using Iterator = cpl::graph::ConnNodeIterator<Graph>;
    using difference_type   = typename Iterator::difference_type;
    using value_type        = typename Iterator::value_type;
    using pointer           = typename Iterator::pointer;
    using reference         = typename Iterator::reference;
    using iterator_category = typename Iterator::iterator_category;
};

Update: Habe nochmal nachgeschaut. Eine Template Spezialisierung beginnt normalerweise mit template<>. Aber in diesem Fall muss man das template<> mit Parameter füllen. Die Spezialisierung funktioniert dennoch :D

Alles nur damit ich das hier so bequem schreiben kann:

NodeID ID = 0;
graph.connNodeRange(ID).size();

11.02.2018

C++ Guns: std::chrono::duration to nice human readable text

Filed under: Allgemein — Tags: — Thomas @ 13:02

std::chrono::duration stellt eine Zeitspanne dar. Zum Beispiel wie lange ein Programm schon läuft. Eine Textausgabe in Sekunde, Minuten, Stunden u.s.w wäre also schön. Die Umrechnung von std::chrono::duration nach Stunden ist super einfach.
Die Hauptarbeit übernimmt std::chrono::duration_cast und vordefinierte Werte für Sekunden, Minunten und Stunden gibt es schon. Die Erweiterung für Tage ist entsprechend einfach.

void nicedateTime(std::chrono::duration<double> dur) {
  typedef std::chrono::duration<int64_t, std::ratio<24*3600>> day;

  auto days = std::chrono::duration_cast<day>(dur);
  dur -= days;
  std::cout << days.count() << "d ";

  auto HH = std::chrono::duration_cast<std::chrono::hours>(dur);
  dur -= HH;
  std::cout << HH.count() << "h ";

  auto min = std::chrono::duration_cast<std::chrono::minutes>(dur);
  dur -= min;
  std::cout << min.count() << "m ";

  auto sec = std::chrono::duration_cast<std::chrono::seconds>(dur);
  std::cout << sec.count() << "s ";
}

08.02.2018

line 1: #!/bin/bash: No such file or directory

Filed under: Allgemein — Tags: — Thomas @ 12:02

$ ./test.sh 
./test.sh: line 1: #!/bin/bash: No such file or directory
$ cat test.sh
#!/bin/bash
$ ll /bin/bash 
-rwxr-xr-x 1 root root 1.1M May 15  2017 /bin/bash

WTF?! Wo ist der Fehler? Ein Blick mit dem hexeditor zeigt seltsame Zeichen am Dateianfang, die im Texteditor nicht sichtbar sind.


$ head -n 1 test.sh | hexdump -C
00000000  ef bb bf 23 21 2f 62 69  6e 2f 62 61 73 68 0a     |...#!/bin/bash.|

Die Hex Zeichenfolge ef bb bf ist ein BOM (Byte order mark) und steht in diesem Fall für die Codierung einer UTF8 Datei. Diese wurde wohl automatisch in die Datei eingefügt. Nur ist Unix älter als Unicode und damals hat sich wirklich keiner dafür interessiert, so dass BOM beim interpretieren der Shellskripte nicht mit interpretiert wird. Braucht man dort auch wirklich nicht.

Leider haben die Unicode Leute nichts seit 1960 gelernt und packen wieder Kontrollzeichen in Plaintext Dateien rein. Mit dem Ergebnis, dass alles inkompatibel bleibt. Plaintext - Klartext, nimm es wörtlich. Für alle die es vergessen haben: Mit plain text (engl. für einfacher, schlichter Text) werden Daten bezeichnet, die direkt unter Verwendung einer Zeichenkodierung in Text umgesetzt werden können. Dies ist mit BOM nicht der Fall. Fail.

Mit vim bekommt man die BOM Bombe leicht wieder weg:


:set nobomb
:wq

$ head -n 1 test.sh | hexdump -C
00000000  23 21 2f 62 69 6e 2f 62  61 73 68 0a              |#!/bin/bash.|
$ ./test.sh 
$

05.02.2018

C++ Guns: Generic Data Type Design Pattern - Teil 4

Filed under: Allgemein — Tags: — Thomas @ 16:02

Die Idee, für User defined Datentypen jede Variable in einem std::tuple oder std::array zu halten, kann nicht überall gut sein. Für jede Variable benötigt man noch eine getter Funktion um der Variablen einen Namen und damit Semantik zu geben. Ich hatte in den letzten Kapitel noch erwähnt, dass es viel Sinn macht, diese Funktionen als const zu deklarieren. Da in den Beispiel Datentypen jede Variablen von der anderen abhängt. Und daher dürfen sie nicht beliebig getrennt verändert werden.

Wann diese Regel nicht mehr gilt ist ganz klar: Wenn Variablen in einem Typ gespeichert werden, die nicht voneinander abhängen. Da frage ich mich natürlich gleich, warum sie dann in einem gemeinsamen Typ gespeichert sind. Aber womöglich ist dieser Übergang ja fließend.

Sowas wie PointReader/Writer hätte als Membervariable natürlich den Path zur Datei, bzw. irgendeine Art von Device/File Handle. Eventuell noch eine Error Variable. Diese beiden Variablen sind natürlich nicht so streng gekoppelt wie xyz, aber gehören dennoch zusammen.

Eigentlich sind das ja Klassen die Verhalten simulieren. Sowas wie ein Widget. Mit Fenstergröße, Position, Farbe, Childwidgets.

Also ich halte mal fest, für Membervaribalen std::tuple std::array nutzen erscheint mir für Massendaten sinnvoll. Sonst nicht. Mal sehn ob sich das in der Praxis bewährt.

02.02.2018

C++ Guns: Generic Data Type Design Pattern - Teil 3

Filed under: Allgemein — Tags: — Thomas @ 23:02

Im letzten Teil hatte ich das serialisieren angesprochen wie das z.B. mit der Qt Klasse (und andere) QDataStream möglich ist.

A data stream is a binary stream of encoded information which is 100% independent of the host computer's operating system, CPU or byte order. For example, a data stream that is written by a PC under Windows can be read by a Sun SPARC running Solaris.

Da nach dem angesprochenen Entwurfsmuster jeder Datentyp von std::array oder std::tuple erbst, braucht es auch nur eine Serialisierungsfunktion für alle User defined Typen. Es fehlt nur eine Möglichkeit den Klassennamen automatisiert anzugeben. So ein bissel C++ reflektion fehlt leider, aber es sollte kein Problem sein den Klassennamen selbst hin zu schreiben,

strcut UserType : public std::tuple<int, double, std::string>
{
  static const char* classname = "UserType";
}

So hätte man eine Möglichkeit, User defined Datentypen automatisiert zu verarbeiten, da sie alle das selbe Interface haben. (Vom Typ std::array oder std::tuple sind).
Ein netter Gedanke.
Aber ich schweife etwas ab. Im vierten Teil versuche ich ein Anwendungsfall zu finden, bei dem dieses Muster nicht mehr passt.

C++ Guns: Generic Data Type Design Pattern - Teil 2

Filed under: Allgemein — Tags: — Thomas @ 16:02

Weiter gehts mit der Anwendung meiner Idee alle einfachen Datentypen als std::array oder std::tuple abzubilden. Wie im ersten Teil schon für den Point2D Datentyp realisiert.

Jetzt werde ich einen Range Datentyp bauen. Dieser besteht einfach aus zwei Zahlen die eine obere und untere Grenze von etwas angibt. Zusätzlich ist die Spanne zwischen obere und untere Grenze interessant. Dies ist aber eine abgeleitete Größe und darf deshalb nicht als gespeichert werden, sondern soll extra berechnet werden.

struct Range : public std::array<double,2>
{
  const auto& lower() const {
    return operator[](0);
  }

  const auto& upper() const {
    return operator[](1);
  }

  const auto size() const {
    return upper()-lower();
  }
}

So, die Intitialisierung erfolgt über eine Liste. Der Zugriff einheitlich über Funktionen. Sowohl für gespeicherte Werte sowie für abgeleitete. Das ist schon mal gut.

Hässliche Member Variablen fallen mit diesem Entwurfsmuster weg. Damit muss man sich und auch nicht mehr überlegen wie man sie nennt. Mit Unterstrich, Groß/Kleinschreibung oder ganz kryptisch und verkrüppelt. Nein, die Entscheidung muss überhaupt nicht mehr gefällt werden.

Da dieses Beispiel doch recht einfach war, versuchen wir es doch mal mit den Stochastischen Momenten.
Also einen Datentyp welche Mittelwert, Varianz und Standardabweichung bereitstellt. Nun kann man aus einer vorher erstellten Summe und der Anzahl der Summanden diese drei Werte immer berechnen. Aber das erscheint mir zu aufwendig und irgendwie nicht richtig. Daher tendiere ich dazu, avg var und std direkt in dem Datentyp abzuspeichern.

Versuchen wir es:

struct StochastikMoment : public std::array<3,double>
{
  template<typename Container>
  StochastikMoment(const Container& data) {
    operator[0] = sum(data)/data.size();
    operator[1] = ...;
    operator[2] = std::sqrt(operator[1]);
  }

  const auto& avg() const {
    return operator[0];
  }

  const auto& var() const {
    return operator[1];
  }

  const auto& std() const {
    return operator[2];
  }
}

Die Initialisierung kann wieder über eine Liste erfolgen, wenn nötig. Aber im Normalfall werden die Momente aus vorgegebenen Daten berechnet. Den Algorithmus dafür spare ich mir.
Der Zugriff Erfolg wie gewohnt über Funktionen. Ich denke an diesem Beispiel wird klar, warum nur const Funktionen in diesem Entwurfsmuster Sinn machen. Die Standardabweichung ist streng verbunden mit der Varianz. Und die Varianz hängt stark vom Mittelwert ab. Es macht einfach keinen Sinn, Mittelwert und Varianz algorithmus zu bestimmen aber die Varianz manuell vorzugegen. Diese drei Werte hängen voneinander ab und treten dementsprechend immer zusammen auf.

Hätte ich mich bei diesem Datentyp anders entschieden und die drei Werte immer wieder berechnet, und die Summe gespeichert, dann wäre das doch auch ganz klar. Wenn man den Mittelwert berechnet, kann man ihn nicht über avg() Setzen.

Ich frage mich ob diese Art und Weise allgemeingültig für alle einfachen Datentypen ist.

Auf jeden Fall ist das serialisieren dadurch einfach. Weiter im dritten Teil.

C++ Guns: Generic Data Type Design Pattern - Teil 1

Filed under: Allgemein — Tags: — Thomas @ 15:02

Hmm das Problem lässt sich vermutlich am besten anhand von Beispielen erklären.

Wie immer nehm ich gern mein Point2D Datentyp her. Es gibt ja verschiedene Wege so etwas zu implementieren.
Eine class mit privaten x/y Variablen und entsprechende getter/setter. Oder ein struct mit nur public x/y Variablen:

strcut Point2D {
 double x, y;
}

Das Problem bei dieser Implementierung ist, dass die x/y Variablen nicht in einem Array sind. Das mag vllt. auf den ersten Blick unnötigt erscheinen, aber im laufe der Jahre habe ich den Zugriff auf xyz anhand von Indizes 1,2,3 öfter mal gebraucht. Die Alternative wäre also:

strcut Point2D {
  std::array<double,2> xy;
}

Aber hier fehlt dann die Möglichkeit gut auszudrücken, dass man explizit auf die x-Variable zugreifen möchte. Wie schnell hat man doch die Index Zahlen verdreht. Eine Kombination beider Variablen ist auch Möglich:

strcut Point2D {
  std::array<double,2> xy;

  auto& x() {
    return operator[](0);
  }

  auto& y() {
    return operator[](1);
  }
}

Die const Varianten der x() y() habe ich jetzt mal weg gelassen. Ich möchte jetzt auf etwas anderes hinweisen: Wie sieht es aus wenn man dieses Konstrukt in seinem Code nutzt:

  Point2D p;
  p[0] = 10;
  p[1] = 20
  cout << p.x() << p.y();
  p.xy[0] = 100;
  p.xy[1] = 200;
  p.xy = {3,4}; ???
  cout << p. xy; ???
  p.x() = 1000;
  p.y() = 2000;

Also ganz ehrlich: Nein. Das ist zu viel des Guten. Zu viele Freiheitsgrade. Das verwirrt doch nur. Und wer die Qual der Wahl hat, trifft die falsche Entscheidung. Vor allem, muss man wirklich die Koordinaten über die Funktion x() und y() setzten können? Das sieht auch ungewohnt aus. Und die extra Variable xy erscheint auch unnötig und überflüssig. Zugriff auf Member, über Funktionen, über Index Operator. Uneinheitlich!

Vor ein paar Monaten bin ich ja auf noch eine weitere Möglichkeit gestoßen, diesen Datentyp zu implementieren. Siehe C++ Guns - C++17 Extension to aggregate initialization. Und ich muss sagen, so schlecht ist das gar nicht. Es bietet eine Menge.

Die Idee ist, den Point2D Datentyp von einem std::array erben zu lassen. Und für die bessere Lesbarkeit noch getter Funktionen mit guten Namen bereit zu stellen. Durch die Vererbung IST der Point2D Datentyp ein Array. Und bietet automatisch den Index Zugriff an. Und durch die getter Funktionen bekommen die einzelnen Arraypositionen eine Semantik.

strcut Point2D : public std::array<double,2>
{
  const auto& x() const {
    return operator[](0);
  }

  const auto& y() const {
    return operator[](1);
  }
}

Nun gestaltet sich die Nutzung schon einfacher. Das Setzten der Werte passiert über eine Initialisierungsliste. Das Lesen über Funktionen. Und in den Fällen wo ein Indexzugriff besser ist, ist das nun auch realisierbar.

  Point2D p {1,2};
  cout << p.x() << p.y();
  p[0] = p[1];

Hier habe explicit nur const getter Benutzt. Dazu sage ich später noch mehr.

Im nächsten Teil werde ich Versuchen, dieses Muster auf andere Datentypen anzuwenden.

Powered by WordPress