C++Guns – RoboBlog blogging the bot

19.06.2023

C++ Guns: How to convert from UTC to local time in C++?

Filed under: Allgemein — Tags: — Thomas @ 11:06

Convert from a broken down date time structure from UTC to localtime in C++.

See Stackoverflow: How to convert from UTC to local time in C?
I converted the code from C to C++ and make it shorter.

// replace non std function strptime with std::get_time

#include <iostream>
#include <sstream>
#include <iomanip>
#include <ctime>

/*
  Convert from a broken down date time structure from UTC to localtime.
  See https://stackoverflow.com/questions/9076494/how-to-convert-from-utc-to-local-time-in-c
*/
std::tm UTC2localtime(std::tm tp) {
  // make sure Daylight saving time flag is not accidentally switched on in UTC time
  tp.tm_isdst = 0;

  // get seconds since EPOCH for this time
  const time_t utc = std::mktime(&tp);
  std::cout << "UTC date and time in seconds since EPOCH: " << utc << "\n";

  // convert UTC date and time (Jan. 1, 1970) to local date and time
  std::tm e0{};
  e0.tm_mday = 1;
  e0.tm_year = 70;

  // get time_t EPOCH value for e0. This handles daylight saving stuff.
  // The value is e.g. -3600 for 1h difference between the timezones
  const time_t diff = std::mktime(&e0);

  // calculate local time in seconds since EPOCH
  const time_t local = utc - diff;
  std::cout << "local date and time in seconds since EPOCH: " << local << "\n";

  // convert seconds since EPOCH for local time into local_tm time structure
  std::tm local_tm;
  if(localtime_r(&local, &local_tm) == nullptr) {
    throw std::system_error(errno, std::generic_category(), "UTC2localtime(): in conversion vom UTC to localtime");
  }
  return local_tm;
}

int main() {
  // hard coded date and time in UTC
  std::string datetime = "2013 11 30 23 30 26";
  std::cout << "UTC date and time to be converted in local time: " << datetime << "\n";

  // put values of datetime into time structure
  std::tm UTC_tm{};
  std::istringstream ss(datetime);
  ss >> std::get_time(&UTC_tm, "%Y %m %d %H %M %S");

  if(ss.fail()) {
    throw std::runtime_error("Can not parse datetime from datetime '" + datetime + "' to format %Y %m %d %H %M %S");
  }

  const std::tm local_tm = UTC2localtime(UTC_tm);
  std::cout << "local date and time: " << std::put_time(&local_tm, "%Y-%m-%d %H:%M:%S %Z") << "\n";
}

15.04.2023

AVR-GCC 13 für Linux installieren / compilieren

Filed under: Allgemein — Thomas @ 20:04

Es gibt bereits eine sehr gute Anleitung für das Installieren vom avr-gcc [1] [2] ..., aber sie sind veraltet. Ich werde im folgenden nur das beste von diesen Artikel übernehmen.

illustration

Zusammenfassung:

Der AVR 8-Bit RISC Mikrocontroller von Atmel ist ein sehr verbreiteter Mikrocontroller. Es ist nur ein Chip mit EEPROM, RAM, Analog zu Digital Umwandler, einer Menge digitaler Ein- und Ausgabezeilen, Timern, UART für RS 232 Kommunikation und vielen anderen Dingen.

Das beste ist jedoch, dass es dafür unter Linux eine vollständige Programmierumgebung dafür gibt: Man kann diesen Mikrocontroller in C++ programmieren, mit dem GCC Compiler.

Die Anleitung ist in mehrere Teile gegliedert. Im ersten beiden Teil wird die Installation erklärt. Im dritten wird gezeigt, wie man damit denn nun seine Programme für den AVR erstellt. Im vierten Teil stehen noch ein paar typische Fehlermeldungen und die Lösungen. Und im letzten ein paar weiterführende Links.

Fertige Pakete

Leider gibt es zu diesem Zeitpunkt (04.2023) keine aktuellen AVR GCC Pakete in den Repositories der Distributionen. Es führt also kein Weg am compilieren vorbei. Wenn die Repositories aktualisiert werden, werde ich das hier vermerken.
Ausnahme bildet das Paket avrdude, dass mit Version 6.3 (2016, Debian 11 bullseye) zwar auch schon etwas älter ist, aber dennoch wird es funktionieren. In der nächsten Debian Version, welche wahrscheinlich im Juni 2023 veröffentlicht wird, ist ein aktuelleres avrdude mit der Version 7.1 dabei. Auf er github Seite von avrdude [7] gibt es Informationen, wie man jetzt schon die neuste Version bekommt.

Avrdude lässt sich mit dem folgenden Befehl aus den Repositories installieren:

# apt-get install avrdude

Eure Voraussetzungen

Ihr müsst grundsätzliche Linux Kenntnisse mitbringen und den Willen sich durch die Anleitung durch zu beißen. Es kann an der ein oder andere Stelle Probleme geben, dann Ruhe bewahren und meist findet man die einfache Lösung schnell von selbst.

AVR GCC compilieren

Also los. Wir brauchen folgende Software:

binutils-2.40.tar.bz2
gcc-13.1.0.tar.gz
avr-libc-2.1.0.tar.bz2

Um bestehende AVR GCC Installationen nicht zu überschreiben, werden die Programme nach ~/bin/avrgcc installiert.

Software Voraussetzungen

Um einem Compiler zu compilieren ist Voraussetzung, dass schon ein Compiler installiert ist - logisch.
Welche Pakete dafür zu installieren sind, variiert etwas zwischen den unterschiedlichen Linux Distributionen. Aber für Debian basierte System ist es im wesentlichen das Paket build-essential sowie das Paket texinfo für binutils.


# apt-get install build-essential texinfo

binutils compilieren

Das binutils Paket enthält alle nötigen low-level Utilities, um Objektdateien zu bauen. Es beinhaltet einen AVR assembler (avr-as), Linker (avr-ld), library handling tools (avr-ranlib, avr-ar), Programme, zum Erzeugen von Objektdateien, die auf das EEPROM des Microcontrollers (avr-objcopy) geladen werden können, disassembler (avr-objdump) und utilities wie avr-strip und avr-size.

Und so wird's gemacht:

$ tar xjf binutils-2.40.tar.bz2
$ cd binutils-2.40/
$ mkdir build
$ cd build
$ ../configure --target=avr --disable-nls --prefix $HOME/bin/avrgcc/
$ make
$ make install

Die Kompilation dauert etwa drei Minuten.

Die binutils ist nun in dem Verzeichnis $HOME/bin/avrgcc/ installiert. Um sie auch aufrufen zu können, muss das Verzeichnis der PATH Umgebungsvariable hinzugefügt werden. Dies geschieht mit folgenden Kommando:


$ export PATH=$HOME/bin/avrgcc/bin/:$PATH

Nun testen wir, ob auch alles klappt. Mit dem Programm 'witch' wird überprüft ob eines der binutils Programme, genauer gesagt das Programm avr-objcopy, erreichbar ist.


$ which -a avr-objcopy
/home/kater/bin/avrgcc/bin/avr-objcopy
/usr/bin/avr-objcopy

Da ich zwei Installationen des avr gcc habe, eine alte Systeminstallation mit dem GCC 5 und die neue mit dem GCC 12, werden bei mir zwei Zeilen ausgegeben. Wichtig ist, dass die neue Installation, also der Path /home/kater/bin/avrgcc/ ganz oben steht, denn dieser wird als erstes durchsucht.
Um das avrgcc Verzeichnis dauerhaft in der PATH Variablen zu seichern, tragt ihr die export Zeile an das Ende der .bashrc Datei ein, die sich im Homeverzeichnis befindet.

AVR GCC compilieren

avr-gcc ist unser eigentlicher Compiler. Diesen zu compilieren ist der aufwendigste Teil, da es am längsten dauert und beim Compilieren am meisten schief gehen kann.
Die für den GCC notwendigen Pakete wie gmp, mpfr, mpc und isl müssen nicht von Hand installiert werden. Dies kann durch das Skript contrib/download_prerequisites erledigt werden. Am besten führt ihr folgende Befehle Schritt für Schritt aus und achtet auf mögliche Fehlermeldungen die kommen könnten. Nicht einfach blind die Befehle copy&pasten!


$ tar xzf gcc-13.1.0.tar.gz
$ cd gcc-13.1.0/
$ ./contrib/download_prerequisites
$ mkdir build
$ cd build/
$ ../configure --target=avr --disable-nls --enable-languages=c,c++ --disable-libssp --with-double=64 --with-long-double=64 --prefix $HOME/bin/avrgcc/
$ make
$ make install

Das Kompilieren hat bei mir etwa 80 Minuten gedauert. Genug Zeit um gemütlich den nächsten Absatz zu lesen ;)

Die Optionen von configure bedeuten folgendes

  • --target=avr Wir wollen für die Zielplattform AVR compilieren -- logisch
  • --disable-nls Native Language Support (NLS) brauchen wir nicht. Es ist sinnvoller die Compiler Meldungen auf englisch zu googlen, dann bekommt man schneller die passende Antwort.
  • --enable-languages=c,c++ Unsere Zielsparache ist C++
  • --disable-libssp libraries for stack smashing protection - Ist sicher einen gute Sache, auch auf einem AVR bestimmt wertvoll, ich mus mal nachschauen warum das in den anderen Anleitungen deaktiviert wurde...
  • --prefix $HOME/bin/avrgcc/ Der Compiler soll in der Homeverzeichnis nach bin/avrgcc installiert werden und nicht in die Systemverzeichnisse.
  • --with-double=64 --with-long-double=64 Den Floatingpoint Typ (long)double mit 64bit compilieren und nicht mit 32.
  • Der letzte Punkt bedarf etwas Erklärung. Wir alle wissen, dass (32/64bit) Gleitkommaberechnungen auf einem 8bit Controller nicht unbedingt die beste Idee ist, da dies sehr viele Instruktionen erzeugt und der Speicherplatz sehr beschränkt ist. Es ist technisch möglich, aber man will es in der Praxis vermeiden. Wahrscheinlich ist das der Grund, warum der double-precision Typ für AVR auch auf 32bit eingestellt ist, statt 64bit. Anders sieht das bei Berechnungen zur Kompilierzeit aus. Diese werden auf einem normalen Computer erledigt, der im Vergleich zum AVR unendlich viel Speicherplatz hat und auch millionenfach schneller ist. Seit C++11 mit constexpr können bequem in normalen Code Berechnungen zur Kompilierzeit ausgeführt werden, es ist kein Gefummelt mit Makros mehr nötig. Und die Möglichkeiten werden mit jeder neuen C++ Version stetig verbessert. Zum Beispiel gibt es in C++23 (GCC13) das Keyword "consteval" welches garantiert, dass eine Funktion nur zur Kompilierzeit ausgeführt wird. Genau das Richtige für Mikrocontroller! Es spricht also nichts dagegen Berechnungen, die zur Kompilierzeit ausgeführt werden, mit 64bit double zu berechnen.

    Hier ein Beispiel das Probleme mit 32bit double Typen aufzeigt: die UBRR Berechnung für den USART. Wie genau die Berechnung aussieht ist dabei egal, wichtig ist nur, dass die Eingangswerte, die CPU Frequenz (z.B 20000000Hz) und die Baudrate (z.B. 1000000), als 32bit integer vorliegen müssen, da die Zahlen für 16bit Typen zu groß sind. Nun kann eine 32bit Ganzzahl nicht verlustfrei in eine 32bit Gleitzahl konvertiert werden, was in einer Warnung (oder Fehler je nach Einstellung) resultiert. Für diesen Fall wissen wir, dass der Verlust an Ziffern kein Problem ist, da wir die möglichen CPU Frequenz und Baud Rate kennen, aber wenn nicht mal dieses eine Beispiel zu 100% mit 32bit double funktioniert, wie soll das erst mit komplizierten Berechnungen funktionieren?!

    Aus diesem Grund habe ich double, wie gewohnt, auf 64bit eingestellt. Wenn man auf dem AVR unbedingt Gleitkommaberechnugen braucht, kann man den Typ float nutzen und alles ist wie immer.

    So, nach diesen Gedanken sollte die Kompilation fertig sein :)

    Zum Abschluss starte ich den Compiler und lasse mir die Versionsnummer ausgeben


    $ avr-gcc --version
    avr-gcc (GCC) 13.1.0

    Weiter geht es mit der Installation der avr-libc

    avr-libc compilieren

    Die Compilation der avr-libc gestaltet sich ähnlich. Folgende Befehle sind auszuführen:


    $ tar xjf avr-libc-2.1.0.tar.bz2
    $ cd avr-libc-2.1.0/
    $ mkdir build
    $ cd build/
    $ ../configure --build=`./config.guess` --host=avr --prefix=$HOME/bin/avrgcc/
    $ make
    $ make install

    Die Kompilation dauert etwa vier Minuten.

    Danach ist die Installation komplett und wir können mit dem Beispiel Projekt weiter machen.

    Ein kleines Testprojekt

    Die Hardware

    Wir beginnen mit einer kleinen Testschaltung, die du dann später erweitern kannst. Unser Testprogramm, so wie es hier dargestellt ist, bringt einfach eine LED zum Blinken. Man kann fertige Experimentierboards nutzen oder sich eine kleine Schaltung selbst zusammen löten. Das Beispiel ist für den ATmega8 geschrieben, kann aber leicht auf andere Typen angepasst werden.

    Schaltplan und Pinbelegung

    Schaltplan und Pinbelegung

    Aufgebaute Schaltung

    Aufgebaute Schaltung

    Ein externer Kristall an dem Mikrocontroller ist für dieses Beispiel nicht notwendig, da der eingebaute Oszillator benutzt wird der von Werk aus auf 1MHz eingestellt ist.

    Die Software

    Ein guter Start für die Programmierung von Mikrocontroller ist das AVR-GCC Tutorial [6] und, natürlich, das Datenblatt des verwendeten Kontrolles.
    Viel besser als alle Theorie ist ein richtiges Beispiel. Wir schreiben ein kleines Programm, das unsere LED blinken lässt. Nicht sehr nützlich, aber sehr gut für den Anfang.
    Ihr könne es hier runterladen: avrblink10zip Das zip enthält den folgenden Quelltext und eine Makefile.

    /*
     * blink.cpp
     * ATmega8 mit 1 MHz
     * PORTB wird ueber ein Timer alle 0.263s ein- und ausgeschaltet. Das entspricht 3.81Hz
     *
     * Der Takt beträgt 1Mhz. Der Timer Prescaler ist auf 1024 eingestellt und ein 8 Bit Timer
     * läuft nach 256 Increments über. Also
     * 1000000Hz / 1024 / 256 = 3.81Hz
     */
    
    #include <avr/io.h>
    #include <avr/interrupt.h>
    
    // Overflow Interrupt Routine von Timer 0
    ISR(TIMER0_OVF_vect) {
      // PORTB inventieren
      PORTB =~ PORTB;
    }
    
    int main() {
      // PORTB als Ausgang schalten
      DDRB  = 0xFF;
      // Alle Ausgaenge auf 0 schalten
      PORTB = 0x00;
    
      // Prescaler von Timer0 auf 1024 stellen
      TCCR0 |= (1 << CS02) | (1 << CS00);
      // Timer 0 Overflow Interrupt aktivieren
      TIMSK  |= (1 << TOIE0);
    
      // Interrupts einschalten
      sei();
    
      // Endlosschleife. Hier ist kein weiterer Code nötig.
      // Das Ein und Ausschalten der LED geschiet in der Overflow Interrupt Routine
      for(;;);
    }
    

    Die reichlichen Kommentare im Quelltext kommentieren ihn hoffentlich ausreichend.
    Kommen wir zum Makefile. Ich hab es extra super super super einfach gehalten, da das Ziel dieses Artikel nicht ist, Makefiles zu schreiben, sondern schnell das Beispiel erfolgreich zu bestreiten.

    all:
            avr-g++ -O1 -Wall -Wextra -Wconversion blink.cpp -mmcu=atmega8 -o blink.elf
            avr-objcopy -O ihex blink.elf blink.hex
    
    load:
            avrdude -p m8 -c avr911 -P /dev/ttyUSB0 -U flash:w:blink.hex:i
    
    clean:
            rm -f *.o *.hex *.elf
    

    Das Makefile besteht aus drei Targets "all", "load" und "clean". Zum compiliere des Quelltest, zum hochladen auf den Mikrocontroller und zum entfernen der compilat Datein. Die Angabe des Mikrocontroller Typs "atmega8" geschiet direkt mit der Compiler Option "-mmcu" und nicht über eine extra Makefile Variable, um die Sache einfacher zu halten. Wenn das Projekt wächst kann das Makefile entsprechend erweitert werden. Achtet unbedingt achten, daß die Einrückungen im Makefile explizit durch TABs erfolgen und nicht durch Leerzeichen! Das ist nervig, aber das ist nunmal so...

    Die Optionen "-Wall -Wextra -Wconversion" schalten viele Warnings ein. Auch solche die Anzeigen wenn einen implizite conversion statt findet. Also z.B. 16bit auf 8bit Integer was auf einem 8Bit Mikrocontroller nicht unbedingt das ist, was man eigentlich programmieren wollte. So kann man viele Stellen finden die möglicherweise Fehler enthalten und potentiell ein paar Instrutionen sparen. Denn das Debuggen auf einem Mikrocontroller ist schwer bis garnicht möglich.

    Allgemeine Fehlermeldungen

    Fehlermeldungen sollten eigentlich keine vorkommen. Wenn doch, vergewissert euch, dass ihr euch genau an die Anleitung gehalten und nichts falsch abgeschrieben habt!

    Hier ein paar typische Fehlermeldungen und ihr Lösungen:

    Permission denied
    Ihr habt nicht die Rechte, um diesen Befehl auszuführen. In dieser Anleitung müssen nur die Befehle zum installieren mit apt-get als root (also mit su oder sudo) ausgeführt werden.

    programmer is not responding
    Es kann keine Verbindung zum Programmer aufgenommen werden: Defektes Kabel, Programmer an der falschen Schnittstelle angeschlossen (default: /dev/ttyS0 statt z.B. /dev/ttypUSB0), kein Strom am Programmer etc.

    Fehlermeldungen beim compilieren von AVR GCC

    /usr/bin/ld: .libs/hwasan.o: relocation R_X86_64_PC32 against undefined symbol `__ehdr_start' can not be used when making a shared object; recompile with -fPIC
    Uh... Ich habe etwas rumgefrage und gegoogelt und am wahrscheinlichsten ist es, dass das Linux einfach zu alt ist. Ich bekam den Fehler bei einem ubuntu 19.04.

    Links

    [1] https://rn-wissen.de/wiki/index.php/Avr-gcc_und_avrdude_installieren by Me 2006, GCC
    [2] https://web.archive.org/web/20080611141419/http://www.tldp.org:80/linuxfocus/English/November2004/article352.shtml Guido Socher 2008, GCC 3.4, Example with ATmega8 (Englisch)
    [3] http://www.linuxfocus.org/Deutsch/November2004/article352.shtml Selber Artikel in Deutsch
    [4] https://web.archive.org/web/20080611143624/http://www.tldp.org/linuxfocus/English/March2002/article231.shtml Guido Socher 2002, GCC 3.0, Example with AT90S4433 (Englisch)

    [5] http://roboblog.fatal-fury.de/?p=4076 GCC 8 compilieren
    [6] https://www.mikrocontroller.net/articles/AVR-GCC-Tutorial
    [7] https://github.com/avrdudes/avrdude/
    [8] https://gcc.gnu.org/install/

    09.04.2023

    Cleanup your messy backup

    Filed under: Allgemein — Thomas @ 13:04

    Cleanup your messy backup by hand could be fun! Kinda of. The goal is to remove duplicated files and organize the files into a new structure.
    First of all lets record the free disk space over time.
    Consider this script which collects the free space over time in a file:

    $ cat freespace.sh 
    date | tr  '\n' ' ' | tee -a freespace_overtime
    df -h . | tail -n 1 | tee -a freespace_overtime
    
    $ cat freespace_overtime 
    Sun 09 Apr 2023 08:24:59 AM CEST /dev/sdb1      1008G  779G  178G  82% /mnt
    

    Now lets try to find some things like local Qt and boost installations, check the downloads/ and opt/ folders, .Trash/ .wine build/ .hg .svg git .local .cache .mozilla .dropbox java .steam bin/ , linux- kernel source, other obsolete downloads and delete them. Also check for hidden directories and hidden files...


    Local Qt installation are usually in the folder "QtSDK" or "Qt-*". The -prune option tell find to stop the recursion when find a matching folder.

    find . -type d -iname "QtSDK*" -prune 
    ./backup/kater_12.01.2018/kater/QtSDK
    
    find . -type d -iname "Qt-*" -prune
    ./backup/kater_12.01.2018/kater/Qt-1.6.0
    
    find . -type d -iname "*.java*" -prune
    

    6GB good start


    Find boost installations

    $ find . -type d -iname "boost*" -prune 
    ./backup/kater_12.01.2018/kater/download/boost_1_64_0
    

    Almost 1GB for me.


    Usually there is a lot of stuff in the downloads folder which can be removed

    $ find . -type d -iname "*download*" -prune
    

    Cleaned up 14GB!


    More obsolete stuff in opt/ folders.

    find . -type d -iname "opt" -prune
    

    Only 1GB this time.


    Find trash bins.

    find . -type d -iname "*trash*" 
    ./.Trash-1000
    ./backup/backup_80GB_HDD/backup_festrechner_13.03.2012/kater/.local/share/Trash
    

    5GB Trash... Be aware don't delete your regular backup files. Because there is no other backup ;)


    Old wine folders can be interesting. ...

    find . -type d -iname ".wine" -prune
    

    But not this time.


    Finding old build folders result in a lot of false positives.

    $ find . -type d -iname "*build*" -prune
    

    2GB for me.


    Old Mercurial hidden folders. Delete them all!

    $ find . -type d -iname ".hg" -prune
    

    And old subversion folders too. Gosch I have over 200 of them

    find . -type d -iname ".svn" -prune |   xargs rm -fr
    

    Don't forget to delete the git folders too! They should be in the cloud anyway ;)

    find . -type d -iname ".git" -prune
    

    Wow 11GB. Guess I deleted some other things too XD


    Nicht wirklich was zu holen bei .local.

     find . -type d -iname ".local" -prune
    

    Immerhin 1GB bei .cache

    find . -type d -iname ".cache" -prune
    

    Nur 700MB. Weg damit. Wenn da was wichtiges gespeichert wäre, hätte man es an anderer Stelle nochmal gespeichert.

    find . -type d -iname ".mozilla" -prune
    

    Dropbox haha als sich "Cloud" anfing durchzusetzen.

     find . -type d -iname "*dropbox*" -prune
    

    Haha Java weine ich keiner Träne nach

    find . -type d -iname "*.java*" -prune
    

    Mit Steam ist das eigentlich so eine Sache, aber auf der anderen Seite kann man alles neu runterladen. Man hat es ja gekauft...

    find . -type d -iname "*.steam" -prune
    

    1.3GB!


    Unglaublich was sich so alles in bin/ Ordner versteckt.

    find . -type d -iname "bin" -prune
    

    Immerhin 6GB.


    Versteckte Ordner sind ja auch so einen Sache von denen man nichts weiß und ergo auch nicht braucht.

    $ find /mnt -type d -iname ".*" -prune
    

    Das waren auch ein paar GB...


    Und versteckte Dateien erst. Was sich da an Müll ansammelt

    $ find /mnt -type f -iname ".*" -prune
    

    Diese dummen autoerstellen thumbnail Dateien können auch weg.

    find . -iname "thumbs.db" -print0 | xargs -0 du -ch
    

    Nur 40MB aber Müll ist Müll.


    Beim erstellen von Half Life Maps entstanden früher jeden Menge Mülldatein beim compilieren. Diese haben die Endung p0 p1 p2 p3 prt pts wic lin max und können nach kurzer Sichtung alle gelöscht werden.

    find . -iname "*.p0" -print0 | xargs -0 du -ch
    find . -iname "*.p1" -print0 | xargs -0 du -ch
    find . -iname "*.p2" -print0 | xargs -0 du -ch
    find . -iname "*.p3" -print0 | xargs -0 du -ch
    find . -iname "*.prt" -print0 | xargs -0 du -ch
    find . -iname "*.pts" -print0 | xargs -0 du -ch
    find . -iname "*.wic" -print0 | xargs -0 du -ch
    find . -iname "*.lin" -print0 | xargs -0 du -ch
    find . -iname "*.max" -print0 | xargs -0 du -ch
    

    Bei den .prt Datein hat sich _EINE_ Datei aus NFSp reingemogelt die nicht gelöscht werden sollte.
    Kanpp 1k Datei und 62M.

    06.11.2022

    skip and hint brushwork in praxis

    Filed under: Allgemein — Thomas @ 20:11

    Hi All!

    I want to show you some skip and hint brushwork in praxis.

    First I'll show you an overview of the map. The big green dot is where I take the sceenshots in game. And the line of sigth is blocked by one of the walls of the small room.


    overview

    But If you enable r_speeds 1 and gl_wireframe 2 in game, you see in the next screenshot pretty much everything from the map is rendered. Although the small building (with the NULL texture on top) blocks line of sight. 1200 wpoly nd 5500 epolys are rendered.


    img1

    To understand this, we'll load vis portal into J.A.C.K. They are stored in the .prt file but first you have to edit the file, otherwise it wont load. Open the file with a simple text editor. Keep the first two lines, then remove every line till the first line with the round brackets. (The number of lines you have to delte is the first number in the file.) It should then look like this:


    img2

    Or use the tool fixprt. You can download it here https://gamebanana.com/tools/6786

    In the next image you see some vis portals in blue which I loaded into J.A.C.K via Menu - Mmap - Load Portal File. There are two vis leafs marked. One in yellow and one in magenta. The start point is in the yellow leaf which goes up to the ceiling. The reason why pretty much everything
    is visible from the startpoint is, because its visible from the yellow portal. It dosen't matter the position of the player.


    img3

    For better understanding I also make a screenshot in J.A.C.K form the opposide of the map where you can see the yellow leaf. As you can see in the next image.


    img4

    In the next immage the player position is in the magenta leaf, right in front of the wall. Now only the sourounding walls of the map itself are rendered (and the lights (func_detail) and some other stuff on the ground (func_illusionary)). The magenta vis leaf is as high as the small room with the NULL texture.


    img5

    How we can achieve this? We have to decrease the hight of the yellow leaf to the hight of the small room. We simply split the leaf with the hint texture. In the next image you can see the brush I made. One side with the skip texture and the other with the skip texture. The hint side face up while will2k suggest to face it down. I don't know if there is any technical reason for this. It shoudlnt be. I like to few my maps from top. So put the hint texture on top of the brush make it easyer to see it in J.A.C.K.


    img6

    After running the compiler and load in the new vis leafs file, the leafs are cutted at the height of the small room. I marked the leafs in magenta as you can see in the next image.


    img7

    I started the game and take a screenshot from the start positon of the player. It works! The wpolys drops from 1200 to 533 and the epolys from 5500 to 375. (56% and 93% saved. It was worth it!)


    img8

    ==============================

    Next I'll show you how vis portals/leafs are good to detect world brushes which are potential good for func_detail.

    Here I made a lamp hanging from the ceiling. I forgot to turn it into a func_detail and as a result, the vis portals/leafs are cutted according to the rotation of the brush. This really looks not right.


    img9

    After changing the light brush to func_detail, run compiler, load prt file, the vis leafs/portals look way better :) And of cource the number of vis leafs/portals decreates, which is good (usually).


    img10

    Thats all for today. Many Thanks to theBoss and bluesed for their patience and endlos tips :)

    Here are somegood links:

    twhl goldsource tutorials https://twhl.info/wiki/page/category%3AGoldsource_Tutorials

    from will2k
    A Systematic Approach to Level Design https://gamebanana.com/tuts/10747
    Planning To Win Sketching Your Level https://gamebanana.com/tuts/10980
    Winning your optimization battle against the source engine https://gamebanana.com/tuts/11178
    Optimization Testing in Source Engine https://gamebanana.com/tuts/11586

    the wall (german) https://thewall.hehoe.de/
    r_speeds und ihre Optimierung https://thewall.hehoe.de/artikel/r_speeds/r_speeds.html

    17.09.2022

    C++ Guns: throw and catch all standard exceptions for fun

    Filed under: Allgemein — Tags: — Thomas @ 19:09

    This example throw and catch all standard exceptions just for fun
    exception list from https://en.cppreference.com/w/cpp/error/exception
    sorted after C++ Standard

    searching a stack trace? look at https://en.cppreference.com/w/cpp/utility/basic_stacktrace

    /* This example throw and catch all standard exceptions just for fun
     * exception list from https://en.cppreference.com/w/cpp/error/exception
     * sorted after C++ Standard
     *
     * searching a stack trace? look at https://en.cppreference.com/w/cpp/utility/basic_stacktrace
     */
    
    #include <iostream>
    #include <exception>
    #include <future>
    #include <regex>
    #include <filesystem>
    #include <chrono>
    #include <any>
    #include <optional>
    #include <variant>
    //#include <format> // C++20. Not implemented in GCC 12
    // #include <expected> // C++23
    
    
    int nCatched = 0;
    
    template<typename T>
    void check(const T& ex, std::string_view exceptionType) {
      nCatched++;
      std::string_view what(ex.what());
      // only compare the first chars from what() message, we are not intereseting in stuff after the exception type
      if(what.substr(0,exceptionType.size()) != exceptionType) {
          std::cerr << exceptionType << " what(): " << ex.what() << "\n";
          std::cerr << "\nERROR: Not all exception derived from " << exceptionType << " catched...\n";
          ::exit(EXIT_FAILURE);
        }
    }
    
    int main() {
    
      bool finish = false;
      int nThrow = 0;
      while(not finish){
        try {
          switch(nThrow) {
            case  1: ++nThrow; throw std::exception();
            case  2: ++nThrow; throw std::logic_error("std::logic_error");
            case  3: ++nThrow; throw std::invalid_argument("std::invalid_argument");
            case  4: ++nThrow; throw std::domain_error("std::domain_error");
            case  5: ++nThrow; throw std::length_error("std::length_error");
            case  6: ++nThrow; throw std::out_of_range("std::out_of_range");
            case  7: ++nThrow; throw std::runtime_error("std::runtime_error");
            case  8: ++nThrow; throw std::range_error("std::range_error");
            case  9: ++nThrow; throw std::overflow_error("std::overflow_error");
            case 10: ++nThrow; throw std::underflow_error("std::underflow_error");
            case 11: ++nThrow; throw std::bad_typeid();
            case 12: ++nThrow; throw std::bad_alloc();
            case 13: ++nThrow; throw std::bad_exception();
            case 14: ++nThrow; throw std::regex_error(std::regex_constants::error_collate); // C++11
            case 15: ++nThrow; throw std::system_error(ENOENT, std::system_category(), "std::system_error"); // C++11
            case 16: ++nThrow; throw std::ios_base::failure("std::ios_base::failure"); // C++11
            case 17: ++nThrow; throw std::future_error(std::future_errc::broken_promise); // C++11
            case 18: ++nThrow; throw std::bad_weak_ptr(); // C++11
            case 19: ++nThrow; throw std::bad_function_call(); // C++11
            case 20: ++nThrow; throw std::bad_array_new_length(); // C++11
            case 21: ++nThrow; throw std::filesystem::filesystem_error("std::filesystem::filesystem_error", std::error_code(ENOENT, std::system_category())); // C++17
            case 22: ++nThrow; throw std::bad_any_cast(); // C++17
            case 23: ++nThrow; throw std::bad_optional_access(); // C++17
            case 24: ++nThrow; throw std::bad_variant_access(); // C++17
            // case 25: throw std::chrono::nonexistent_local_time(); // C++ 20. Not implemented in GCC 12
            // case 26: throw std::chrono::ambiguous_local_time(); // C++20. Not implemented in GCC 12
            // case 27: throw std::format_error(); // C++20. Not implemented in GCC 12
            // case 28: throw std::bad_expected_access(); // C++23
            // case 29: throw std::tx_exception(); TODO
            default: {
              finish = true;
            }
          }
        }
        catch(std::bad_variant_access& ex) {
          check(ex, "bad variant access");
        }
        catch(std::bad_exception& ex) {
          check(ex, "std::bad_exception");
        }
        catch(std::bad_array_new_length& ex) {
          check(ex, "std::bad_array_new_length");
        }
        catch(std::bad_alloc& ex) {
          check(ex, "std::bad_alloc");
        }
        catch(std::bad_function_call& ex) {
          check(ex, "bad_function_call");
        }
        catch(std::bad_weak_ptr& ex) {
          check(ex, "bad_weak_ptr");
        }
        catch(std::bad_optional_access& ex) {
          check(ex, "bad optional access");
        }
        catch(std::bad_any_cast& ex) {
          check(ex, "bad any_cast");
        }
        catch(std::bad_typeid& ex) {
          check(ex, "std::bad_typeid");
        }
        catch(std::filesystem::filesystem_error& ex) {
          check(ex, "filesystem error");
        }
        catch(std::ios_base::failure& ex) {
          check(ex, "std::ios_base::failure");
        }
        catch(std::system_error& ex) {
          check(ex, "std::system_error");
        }
        catch(std::regex_error& ex) {
          check(ex, "Invalid collating element in regular expression"); // regex_error.what does not print the exception type first...
        }
        catch(std::underflow_error& ex) {
          check(ex, "std::underflow_error");
        }
        catch(std::overflow_error& ex) {
          check(ex, "std::overflow_error");
        }
        catch(std::range_error& ex) {
          check(ex, "std::range_error");
        }
        catch(std::runtime_error& ex) {
          check(ex, "std::runtime_error");
        }
        catch(std::future_error& ex) {
          check(ex, "std::future_error");
        }
        catch(std::out_of_range& ex) {
          check(ex, "std::out_of_range");
        }
        catch(std::length_error& ex) {
          check(ex, "std::length_error");
        }
        catch(std::domain_error& ex) {
          check(ex, "std::domain_error");
        }
        catch(std::invalid_argument& ex) {
          check(ex, "std::invalid_argument");
        }
        catch(std::logic_error& ex) {
          check(ex, "std::logic_error");
        }
        catch(std::exception& ex) {
          check(ex, "std::exception");
        }
      } // while
    
    
      if(nThrow != nCatched) {
        std::cerr << nThrow << " exception thrown but " << nCatched << " catched\n";
      } else {
        std::cout << "All exceptions which was thrown was catched\n";
      }
    
      return EXIT_SUCCESS;
    }
    

    $ ./a.out
    All exceptions which was thrown was catched

    07.09.2022

    C++ Guns: Streams display the format flags

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

    Display which format flags are currently set e.g. fixed, scientific, dec, hex

    std::ostream& operator<<(std::ostream& s, const std::ios::fmtflags f) {
       if(f & std::ios::boolalpha) s << "boolalpha ";
       if(f & std::ios::dec) s << "dec ";
       if(f & std::ios::hex) s << "hex ";
       if(f & std::ios::oct) s << "oct ";
       if(f & std::ios::fixed) s << "fixed ";
       if(f & std::ios::scientific) s << "scientific ";
       if(f & std::ios::right) s << "right ";
       if(f & std::ios::left) s << "left ";
       if(f & std::ios::internal) s << "internal ";
       if(f & std::ios::showbase) s << "showbase ";
       if(f & std::ios::showpoint) s << "showpoint ";
       if(f & std::ios::showpos) s << "showpos ";
       if(f & std::ios::uppercase) s << "uppercase ";
       if(f & std::ios::unitbuf) s << "unitbuf ";
       if(f & std::ios::skipws) s << "skipws ";
       return s;
    }
    
    std::cout << std::cout.flags() << "\n";
    

    Example output

    dec fixed skipws 
    

    https://en.cppreference.com/w/cpp/io/ios_base/flags

    03.08.2022

    Fenster Wärmedämmen (Sommer & Winter)

    Filed under: Allgemein — Thomas @ 09:08

    02.08.2022

    Im zweiten Versuch belege ich ein Fenster im Flur komplett mit 4cm Styropor und wiederhole die Temperatur Messung zwischen gedämmten und ungedämmten Fenster. Das Styropor selbst habe ich auf einer Seite mit Farbe und Leim etwas widerstandsfähiger gemacht. Die Seite welche an das Fensterglas kommt bleibt unbehandelt.

    fenster1

    Die Messreihe zeigt wie erwartet eine deutlich geringere Temperatur am Fenster mit Styropor. Es stellte sich allerdings nur ein maximaler Temperaturunterschied von 4.3 grad ein. Allerdings war die Tageshöchsttemperatur auch ein paar Gerade geringer als bei der letzten Messung. Interessant ist noch der Peak um etwa 18:45 Uhr. Hier scheint die Sonne sich noch einmal kurz einen Weg zwischen den Gebäuden gesucht zu haben.

    fenster_02.08.2022


    19.07.2022

    Im Sommer scheint die Sonne mit runter gelassenen Rolladen stark durch die (einfach verglaste) Fenster, man spürt richtig die Wärme mit der Hand. In der Garage liegt noch etwa 2cm dickes Styropor. Das werde ich probeweise außen am Fenster zwischen Glas und Rolladen klemmen, so dass eine Dämmung entsteht. Das Stück ist nicht groß genug für das komplette Fenster, aber so 1/4 sollte es abdecken. Wichtig ist hierbei, dass das Styropor dicht am Glas anliegt und keine Luft dazwischen zirkulieren kann, sonst ist der Dämmeffekt damit.

    Um ein paar Zahlen zu bekommen werde ich innen am Glas die Temperatur messen, sowie von einem ungedämmten Fenster. Und da noch zwei weitere Temperaturfühler zur Verfügung stehen auch noch Wand und Raumtemperatur.

    Es zeigt sich ein Unterschied von 5 grad zwischen gedämmten und ungedämmten Fenster. Das ist vielversprechend! Mit besser zugeschnittenen und mehr verdeckte Fläche sollte der Effekt größer sein.

    fenster_19.07.2022

    30.05.2022

    C++ Guns: MPI Dataype; send struct

    Filed under: Allgemein — Tags: — Thomas @ 10:05

    Das Beispiel habe ich von https://www.mpi-forum.org/docs/mpi-3.1/mpi31-report/node425.htm
    Jeder Thread erstellt einen MPI Datentyp welcher die Offsett Addressen der struct Member Variablen hat.
    Thread 1 sendet Daten zu Thread 0
    Thread 0 empfaenge Daten von Thread1 und seine eigenen Daten, so dass alle in einem Array dann liegen.

    // This example is based on https://www.mpi-forum.org/docs/mpi-3.1/mpi31-report/node425.htm
    // Jeder Thread erstellt einen MPI Datentyp welcher die Offsett Addressen der struct Member Variablen hat.
    // Thread 1 sendet Daten zu Thread 0
    // Thread 0 empfaenge Daten von Thread1 und seine eigenen Daten, so dass alle in einem Array dann liegen.
    
    #include <iostream>
    #include <array>
    
    #include <mpi.h>
    
    int my_rank;
    int nThreads;
    
    struct basetype_t {
      double volstep = 0;
      double volTot = 0;
    };
    
    // Vererbung ist nicht erlaubt
    struct type_t  {
      char ID[50];
      int i = 0;
      float x = 0;
      // Ein Container wie std::vector zwischen den Datentypen scheint wohl zu funktionieren, ist in Fortran aber explizit nicht erlaubt.
    //   std::vector<int> unused;
      bool l = false;
      double d = 0;
      basetype_t base;
    };
    
    // Check MPI Error code
    void check(int ierr) {
      if (ierr != MPI_SUCCESS) {
        char err_recvbuffer[MPI_MAX_ERROR_STRING];
        int resultlen;
        MPI_Error_string(ierr, err_recvbuffer, &resultlen);
        std::cerr << err_recvbuffer << "\n";
        MPI_Finalize();
      }
    }
    
    // create new MPI datatype based on the addresses of the member variables of the type we want to send
    MPI_Datatype createMPItyp() {
      type_t foo;
      MPI_Aint base;
      check(MPI_Get_address(&foo, &base));
    
      // Fuer jede member Variable die gesendet werden soll, Typ und Addresse bestimmen
      const int nMembervarsToSend = 7;
      std::array<MPI_Datatype, nMembervarsToSend> types;
      std::array<int,nMembervarsToSend> blocklen;
      std::array<MPI_Aint, nMembervarsToSend> disp;
    
      types[0] = MPI_INT;
      blocklen[0] = 1;
      check(MPI_Get_address(&foo.i, &disp[0]));
    
      types[1] = MPI_FLOAT;
      blocklen[1] = 1;
      check(MPI_Get_address(&foo.x, &disp[1]));
    
      types[2] = MPI_LOGICAL;
      blocklen[2] = 1;
      check(MPI_Get_address(&foo.l, &disp[2]));
    
      types[3] = MPI_DOUBLE;
      blocklen[3] = 1;
      check(MPI_Get_address(&foo.d, &disp[3]));
    
      types[4] = MPI_CHAR;
      blocklen[4] = sizeof(foo.ID);
      check(MPI_Get_address(&foo.ID, &disp[4]));
    
      types[5] = MPI_DOUBLE;
      blocklen[5] = 1;
      check(MPI_Get_address(&foo.base.volstep, &disp[5]));
    
      types[6] = MPI_DOUBLE;
      blocklen[6] = 1;
      check(MPI_Get_address(&foo.base.volTot, &disp[6]));
    
      if(my_rank == 0) {
          std::cout << "Base Address " << std::hex << base << "\n";
          std::cout << "Addresses   ";
          for(auto& x : disp) {
            std::cout << " " << std::hex << x;
          }
          std::cout << std::dec << "\n";
      }
    
      // Addresse zu Offset umrechnen
      for(auto& x : disp) {
        x -= base;
      }
    
      if(my_rank == 0) {
        std::cout << "Displacement";
        for(auto& x : disp) {
          std::cout << " " << x;
        }
        std::cout << "\n";
      }
    
      MPI_Datatype newMPItype;
      check(MPI_Type_create_struct(nMembervarsToSend, blocklen.data(), disp.data(), types.data(), &newMPItype));
      check(MPI_Type_commit(&newMPItype));
    
      return newMPItype;
    }
    
    void doRank0(MPI_Datatype newMPItype) {
        type_t sendbuffer;
        strcpy(sendbuffer.ID, "Kreis100");
        sendbuffer.i = 10;
        sendbuffer.x = 1.2f;
        sendbuffer.d = 1.23;
        sendbuffer.l = true;
        sendbuffer.base.volstep = 1.34;
        sendbuffer.base.volTot = 1.56;
    
        int  displacements[nThreads], counts[nThreads];
    
        std::vector<type_t> recvbuffer(2);
        std::cout << my_rank << " Receiving...\n";
    
        int root_rank = 0;
        displacements[0] = 0;
        displacements[1] = 1;
        counts[0] = 1;
        counts[1] = 1;
        // MPI_Gatherv(recvbuffer_send,count_send, datatype_send, recvbuffer_recv, counts_recv, displacements, datatype_recv, root,comm)
        check(MPI_Gatherv(&sendbuffer, 1, newMPItype, recvbuffer.data(), counts, displacements, newMPItype, root_rank, MPI_COMM_WORLD));
        std::cout << my_rank << " Done receiving\n";
    
        std::cout << my_rank << " content of struct:\n";
        for(const type_t& buf : recvbuffer) {
          std::cout << "ID "  << buf.ID << "\n";
          std::cout << "i "  << buf.i << "\n";
          std::cout << "x "  << buf.x << "\n";
          std::cout << "d "  << buf.d << "\n";
          std::cout << "l "  << buf.l << "\n";
          std::cout << "volstep "  << buf.base.volstep << "\n";
          std::cout << "volTot "  << buf.base.volTot << "\n\n";
        }
    }
    
    void doRank1(MPI_Datatype newMPItype) {
        type_t sendbuffer;
        int  displacements[nThreads], counts[nThreads];
    
        strcpy(sendbuffer.ID, "Kreis200");
        sendbuffer.i = 20;
        sendbuffer.x = 2.2;
        sendbuffer.d = 2.23;
        sendbuffer.l = true;
        sendbuffer.base.volstep = 2.34;
        sendbuffer.base.volTot = 2.56;
    
        std::cout << my_rank << " Sending...\n";
        // MPI_Gatherv(recvbuffer_send,count_send, datatype_send, recvbuffer_recv, counts_recv, displacements, datatype_recv, root,comm)
        int root_rank = 0;
        check(MPI_Gatherv(&sendbuffer, 1, newMPItype, NULL, counts, displacements, newMPItype, root_rank, MPI_COMM_WORLD));
        std::cout << my_rank << " Done sending\n";
    }
    
    int main(int argc, char* argv[]) {
        MPI_Init(&argc, &argv);
    
        // Get number of processes and check only 2 processes are used
        MPI_Comm_size(MPI_COMM_WORLD, &nThreads);
        if(nThreads != 2) {
            std::cout << "Start with 2 threads.\n";
            MPI_Abort(MPI_COMM_WORLD, EXIT_FAILURE);
        }
    
        MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
        MPI_Datatype newMPItype = createMPItyp();
    
        switch(my_rank) {
          case 0: doRank0(newMPItype); break;
          case 1: doRank1(newMPItype); break;
        };
    
        MPI_Finalize();
    
        return EXIT_SUCCESS;
    }
    
    

    $ mpic++ -g -ggdb -Wall test_MPI_struct.cpp

    $ mpiexec -n 2 ./a.out
    1 Sending...
    1 Done sending
    Base Address 7ffd83f7e180
    Addresses 7ffd83f7e1b4 7ffd83f7e1b8 7ffd83f7e1bc 7ffd83f7e1c0 7ffd83f7e180 7ffd83f7e1c8 7ffd83f7e1d0
    Displacement 52 56 60 64 0 72 80
    0 Receiving...
    0 Done receiving
    0 content of struct:
    ID Kreis100
    i 10
    x 1.2
    d 1.23
    l 1
    volstep 1.34
    volTot 1.56

    ID Kreis200
    i 20
    x 2.2
    d 2.23
    l 1
    volstep 2.34
    volTot 2.56

    14.05.2022

    Kaffeetassenwärmer Optimierungsaufgabe

    Filed under: Allgemein — Tags: , — Thomas @ 07:05

    Niemand mag kalten Kaffee! Darum muss ein Kaffeetassenwärmer her! Aber das Zeug was man kaufen kann taugt alles nichts! USB2/1 liefert nicht genügend Leistung und USB3 ist eine Vergewaltigung der kleine Kabelchen. Noch dazu habe ich keinen USB3 Port am Laptop. Eine simple Heizplatte mit einem EIN/AUS Schalter langt doch vollkommen. So wie die in meiner Kaffeemaschine....

    In der Bastelkiste finden sich noch ein paar Hochlast Widerstände. Und ein Spielzeug Transformator, welcher ca 16W Leistung liefert, ist auch noch vorhanden. Da müsste sich doch eine passende Kombination von Widerständen finden lassen?! Meine Versuche von Hand zeigen ein mögliches, aber nicht optimales Ergebnis: kein Widerstand wird überlastet, die Leistung in der Summe ist auch okay, aber die Widerstände werden sehr ungleich belastet. Einige bleiben fast kalt, andere werden sehr heiß.

    Ein Computerprogramm soll es lösen!

    Jede Kombination aus Widerstand, parallel und seriell Schaltung sollte von der Laufzeit her kein Problem sein. Aber da ich momentan keine Idee habe, wie man da alle Möglichkeiten durchgeht, will ich es erst mal als Optimierungsproblem beschreiben.

    Die constraints sind, dass kein Widerstand überlastet wird. Also, dass die elektrische Leistung gebildet aus Spannung und Strom nicht über die maximale Leistung des Widerstandes liegt.
    Die fitness Funktion berechnet die elektrische Leistung aller Widerstände, diese soll maximal sein.

    Da die zufällig erstellte Schaltung durchaus komplex werden kann, langen die einfachen Rechenregeln für parallel und seriell Schaltung nicht. Da wird sich ein elektrisches Netzwerk bilden mit Knoten und Schleifen Regeln. Kirchhoffsche Regeln ... die werden in eine Matrix übersetzt die man dann löschen kann. Ich glaube so war das.

    Mein erster Versuch sieht so aus:

    --+--| 47 |--+----+--| 68 |------------+---
      +--| 47 |--+    |                    |
      +--| 47 |--+    +--| 33 |--+--| 1 |--+
      +--| 47 |--+    +--| 12 |--+
    

    Eine parallel Schaltung aus vier 47 Ohm Widerständen. Das ergibt 47/4=11.75 Ohm. Plus eine parallel Schaltung von 68 Ohm mit einer Kombination einer parallel Schaltung von 33 mit 12 Ohm in Reihe zu 1 Ohm. Das ergibt in der Summe (gemessen) 20.9 Ohm. Bei einer Spannung von 16V ergibt das ein Strom von 16/20.9=0.77A und einer Leistung von 16*0.77=12.3W Ob das langt, den Kaffee warm zu halten, muss noch ermittelt werden ;)

    Hier ein Bild des fliegenden Aufbaus:
    kaffeetasse1

    16.02.2022

    C++ Guns: Play with std::tuple and std::apply

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

    Part 1: print std::array with std::integer_sequence
    Part 2: convert tuple to parameter pack
    Part 3: print std::array with std::apply and fold
    Part 4: fold over std::tuple und erzeugten Assembler Code
    Part 5: fold over std::tuple of std::vector of Types ...
    Part 6: apply generic lambda to tuple
    Part 7: Play with std::tuple and std::apply

    #include <tuple>
    #include <string_view>
    #include <iostream>
    
    void print(int i, std::string_view str, double x) {
        std::cout << i << " " << str << " " << x << "\n";
    }
    
    
    
    int main() {
        std::tuple tp {1, "abc", 3.1};
    
        // Ein Tuple in seine Einzelteile zerlegten durch eine Funktion die genau
        // die Tuple Elemente als Parameter hat
        std::apply(print, tp);
    
    
        // Ein Tuple in sein erstes Element und den Rest zerlergen.
        // Der Rest ist ne variadic argument list
        // Die Funktion print2 ist absichtlich ein Lambda, 
        // da das sonst mit dem auto Argument Typ nicht funktioniert. 
        // Will man print2 als freie Funktion haben, muss man sie als template schreiben.
        auto print2 = [](const int i, const auto&... restArgs) {
            std::cout << i << " Anzahl Restargumente: " << sizeof...(restArgs) << "\n";
        };
        std::apply(print2, tp);
    }
    

    1 abc 3.1
    1 Anzahl Restargumente: 2

    « Newer PostsOlder Posts »

    Powered by WordPress