C++Guns – RoboBlog blogging the bot

13.06.2018

C++ Guns: fold over std::tuple of std::vector of Types ...

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

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

Okay das ist ein seltsames Konstrukt. Wir haben viele Instanzen vieler Typen über die alle zusammen iteriert wird. Eigentlich ist der Code ziemlich klar. Ansonsten.... WOW was alles mit C++17 geht.

#include <type_traits>
#include <tuple>
#include <vector>
#include <iostream>

template<typename... Args>
struct A : std::tuple<std::vector<Args>...>
{   
    template<typename T>
    void add(const T&x) {
        std::get<std::vector<T>>(*this).push_back(x);
    }
};

namespace std {
    template<typename...Args>
    struct tuple_size<A<Args...>> : std::integral_constant<std::size_t, sizeof...(Args)> {};
}

auto func() {
    A<int,double> a;
    a.add(1);
    a.add(2);
    a.add(10.1);
    a.add(20.1);
    
    return a;
}

template<typename T>
std::ostream& operator<<(std::ostream& s, const std::vector<T>& vec) {
    for(const auto& x : vec) {
        s << x << ' ';
    }
    return s;
}

void func3() {
    auto a = func();
    std::apply([&](const auto&...vectors){ (std::cout << ... << vectors);  }, a);  
}

C++ Guns: nächste darstellbare Gleitkommazahl : std::nextafter

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

std::nextafter(Arithmetic from, Arithmetic to)

Returns the next representable value of from in the direction of to.

Die nächste Zahl nach 1.0f ist dann 1.0000001192092895508. Hab das mal für eine Reihe von Zahlen mir ausgeben lassen. Und nicht nur die nächste Zahl, sondern bis zu 1000 nächst Zahlen.

nextafter

Hier das Programm für C++:

double nextafter2(double x, int anzahl) {
    for(int i=0; i < anzahl; i++) {
        x = std::nextafter(x, x+x);
    }
    return x;
}

void blah(int i) {
    std::cout << i << "     ";
    std::cout << nextafter2(0.001, i)- 0.001 << " ";
    std::cout << nextafter2(0.01, i)  - 0.01 << " ";
    std::cout << nextafter2(0.1, i)    - 0.1 << " ";
    std::cout << nextafter2(1, i)        - 1 << " ";
    std::cout << nextafter2(10, i)      - 10 << " ";
    std::cout << nextafter2(100, i)    - 100 << " ";
    std::cout << nextafter2(1000, i)  - 1000 << "\n";
}

    std::cout << "anzahl   0.001       0.01       0.1       1.0       10      100       1000\n";
    blah(1);
    blah(10);
    blah(100);
    blah(1000);

Und hier das Programm für gnuplot

set title "std::nextafter"
set format y "%E"
set key left
set key autotitle columnhead
set ylabel "relativ next"
set xlabel "ith next number"
set logscale xy
plot for [col=2:8] "nextnumber" using 1:col  w linespoint

05.06.2018

C++ Guns: fold over std::tuple und erzeugten Assembler Code

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

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

Hier ein Ausschnitt aus dem Kniffel Spiel Code. Habe jede gültige Kombination als einen Typ dargestellt mit einem gemeinsamen Typ Score, welcher die erzielten Punkte enthält. Um die gesamte Punktzahl zu erhalten, muss über jeden Typ im tuple iteriert werden und die einzelnen Punkte aufaddiert. Dies wird mit std::apply und fold erledigt.
Alternativ dazu wird in func2() nur über sieben Score Objekte iteriert und auf addiert. Mit Compiler Optimierung O1 ist der generierte Assembler Code mit dem std::apply und fold definitiv besser. Es werden sechs Addition Instruktionen erzeugt. Genau soviel wie benötigt werden um sieben Zahlen zu generieren ;)

Der Assembler Code von func2() hingegen zeigt eine Schleife. Dies finde ich bei so kleinen Datenmengen unnötig. Natürlich kann man mit Optimierung O3 loop unrolling aktivieren, aber der erzeugte Code wird damit noch schlechter. Die manuelle Aktivierung mit O1 und -funroll-loops oder -fpeel-loops erzeugt am Ende auch nur sechs Addition Instruktionen, aber dann ist loop unrolling für alle fix size Schleifen aktiviert. Auch für die, wo man es lieber nicht haben möchte.

Und natürlich geht mit der func2() Variante die Typ Information verloren. Also, std::apply und fold um über Typen zu iterieren. Find ich gut.

struct Score 
{
    int points = 0;
    
};

struct Dreierpasch : Score {
};

struct Viererpasch : Score {
};

struct FullHouse : Score {
};

struct KleineStrasse : Score {
};

struct GrosseStrasse : Score {
};

struct Kniffel : Score {
};

struct Chance : Score {
};

struct LowerScore;
namespace std {
    template<>
    struct tuple_size<LowerScore> : std::integral_constant<size_t,7> {};
}

struct LowerScore : std::tuple<Dreierpasch, Viererpasch, FullHouse, KleineStrasse, GrosseStrasse, Kniffel, Chance>
{
    int totalPoints() const {
        auto f = [](const auto...x){ return (x.points + ...); };
        return std::apply(f, *this );
    }
};

auto func(const LowerScore& score) {
    return score.totalPoints();
}

auto func2(const std::array<Score,7>& score) {
    int points = 0;
    for(const Score& s : score) {
        points += s.points;
    }
    return points;
}
func(LowerScore const&):
  movl (%rdi), %eax
  addl 4(%rdi), %eax
  addl 8(%rdi), %eax
  addl 12(%rdi), %eax
  addl 16(%rdi), %eax
  addl 20(%rdi), %eax
  addl 24(%rdi), %eax
  ret
func2(std::array<Score, 7ul> const&):
  leaq 28(%rdi), %rdx
  movl $0, %eax
.L3:
  addl (%rdi), %eax
  addq $4, %rdi
  cmpq %rdx, %rdi
  jne .L3
  ret

02.06.2018

C++ Guns: convert tuple to parameter pack

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

Part1: print std::array with std:integer_sequence
Part2: 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

Guckst du hier https://en.cppreference.com/w/cpp/language/parameter_pack
und hier https://en.cppreference.com/w/cpp/language/fold
und da https://stackoverflow.com/questions/47216057/how-to-write-a-fold-sum-function-for-c-tuple

parameter pack zu std::tuple ist einfach

template<typename...Ts>
auto func(const Ts&...args) {
    return std::tuple(args...);
}

auto func2() {
    return func(1, 2.0, "test");
}

https://stackoverflow.com/questions/47216057/how-to-write-a-fold-sum-function-for-c-tuple

Der template Type Ts besteht aus den drei einzelnen Typen int, double und const char*. Die Variable args... hält entsprechend die drei Werte. Das sieht man an den drei Punkten "..." bei der Type und Funktions Argument Definition. Die sagen aus, dass hier noch mehr kommt. Die drei Punkte beim erstellen vom tuple packen den parameter pack aus. Sinngemäß wird der Ausdruck umgewandelt zu

return std::tuple<int,double,const char>{arg1, arg2, arg3};

Andersherum, also vom tuple zum parameter pack ist nicht so einfach. Ein Weg führt über std::integer_sequence wie ich es im anderen Post gezeigt habe. Aber das ist wieder so viel Template gedöns und man braucht zwei Funktionen. Ein anderer Weg führt über C++17 std::apply und fold, wie in dem stackoverflow Post gezeigt. Man benötigt zeitgleich aber eine Funktion, welche beim fold auf die Argumente angewandt werden kann. Das ganze bewegt sich weg von dem WIE etwas passiert, hin zu dem WAS passieren soll. Dem bin ich positiv gegenübergestellt.

auto t = std::make_tuple( 1, 2.0 );
auto func = []( auto... v ){ return ( v + ... ); };
auto sum = std::apply(func, t);

In std::apply steckt eine std::integer_sequence drin, welche aus dem tuple erstellt und der Funktion übergeben wird. damit haben wir wieder ein parameter pack. Mittels lambda Funktion spart man sich auch wieder die Templates. Letztendlich müssen wir mit dem parameter pack irgendetwas tun, also wird der plus operator mittels fold aufgerufen.

Das ist schon eine ganz andere Art zu Programmieren, so ganz ohne explizite Schleifen. Aber ich finde es gut. Verhindert Fehler und der Compiler kann mehr tun.
Zum Abschluss noch ein kurzer Spaß. Was passiert, wenn man ein tuple in ein parameter pack umwandelt und das zurück in ein tuple? Und wie sieht der Assembercode dazu aus? Probieren wir es aus!

auto func(const std::tuple<int,double,const char*>& t) {
    auto f = [](const auto&...v){ return std::tuple{v...}; };
    return std::apply(f, t);   
}
func(std::tuple<int, double, char const*> const&):
  movq %rdi, %rax
  movq (%rsi), %rdx
  movq %rdx, (%rdi)
  movsd 8(%rsi), %xmm0
  movsd %xmm0, 8(%rdi)
  movl 16(%rsi), %edx
  movl %edx, 16(%rdi)
  ret

Nun es passiert nichts weiter ;) Es gibt Sieben Assembler Instruktionen. Im Prinzip werden die Argumente nur kopiert. Jede Kopieraktion braucht zwei Instruktionen, da nicht von Speicher zu Speicher kopiert werden kann. Nur von Speicher zu Register und von Register zu Speicher. Die Argumente stehen im rsi Register. Wenn mich nicht alles täuscht, wird erst der const char Pointer kopiert. Also ein quadword mit 64 Byte. Danach kommt das double, erkennt man am xmm0 Register. Und zuletzt das int mit einer movl Instruktion.

C++ Guns: print std::array or std::tuple with std::apply and fold

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

Part 1: print std::array with std:integer_sequence
Part 2: convert tuple to parameter pack
Part 3: print std::array or std::tuple 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

Da ein std::array eigentlich auch nur so etwas wie ein tuple ist, nur nicht mit unterschiedlichen Typen, kann die ganze parameter pack und fold Sache auch auf std::array angewandt werden.

template<typename T, size_t N>
std::ostream& operator<<(std::ostream& s, const std::array<T,N>& arr) {
    std::apply([&](const auto&...v){ (s << ... << v);  }, arr); 
    return s;
}
 
void func(const std::array<double,2>& arr) {
    std::cout << arr;
}

Ja, eigentlich ganz hübsch.

01.06.2018

C++ Guns: Ein Paar Würfel

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

Ich möchte zum Spaß ein Wüfelspiel programmieren. Dazu müssen einzelne Würfel implementiert werden. Doch was ist ein Würfel eigentlich? Offensichtlich ein Zufallszahlengenerator z.B. std::minstd_rand. Aber ein Würfel repräsentiert auch eine Gleichverteilung Ganzer Zahlen im Bereich von z.B. 1 bis 6. Und ein Würfel hat ein Status, seine Augenzahl. Die ändert sich ja nicht, egal wie oft man einen Würfel anschaut. Außer der Würfel wird neu geworfen, also gibt es eine roll() Funktion.
Und, ganz wichtig: Ein Würfel kann default initialisiert werden. Denn sobald ein Würfel existiert, zeigt er seine Augenanzahl an. Diese ist natürlich zufällig, aber existiert immer.

Zusammen mit meinem Generic Data Type Design Pattern sieht der Code etwas komisch aus, macht aber genau was er soll. Im Unterschied zu vorherigen Implementationen dieses Pattern ändern ein Würfel ständig seinen Zustand. Daher gibt es neben den einheitlichen const getter Funktionen z.B. number(), diesmal auch non-const setter, welcher aber als privat! deklariert sind. Denn nur der Würfel selbst darf seinen Zustand ändern.

Für mich wichtig ist, dass das Erstellen und Benutzen von Würfel-Objekten einfach und intuitiv ist. Sobald ein Würfel erstellt worden ist, ist er da und hat eine Zahl. Wenn der Würfel neu geworfen wird, ändert sich die Zahl. Ansonsten kann der Würfel nichts. Und so ist es doch gut.

Was mich noch richtig stört ist das seedn der Zufallgeneratoren. Jeder Würfel IST EIN Zufallsgenerator. Also muss der Seed immer unterschiedlich sein. Die Zeit in Sekunden kann hierfür nicht genommen werden, diese Ändert sich ja nur jede Sekunde. Daher wird noch auf einen realen Hardware Zufallsgenerator zugegriffen und der endgültige Seed mit std::seed_seq erstellt. std::seed_seq selbst darf aber kein temporäres Objekt sein, da der Zufallszahlengenerator beim initialisieren das std::seed_seq Objekt verändern darf.

template<int N=6>
struct Dice : std::tuple<std::minstd_rand, std::uniform_int_distribution<int>, int> {
    Dice() {
        // Dreck.
        std::seed_seq seed{unsigned(42), std::random_device()(), unsigned(std::time(nullptr))};
        gen() = std::minstd_rand(seed);
        dis() = std::uniform_int_distribution<int>(1,N);
        number() = dis()(gen());
    }
    
    auto roll() {
        number() = dis()(gen());
        return number();
    }
    
    auto number() const {
        return std::get<2>(*this);
    }
    
private:
    auto& gen() {
        return std::get<0>(*this);
    }
    
    auto& dis() {
        return std::get<1>(*this);
    }
    
    auto& number() {
        return std::get<2>(*this);
    }
};

template<int M>
struct Dices : std::array<Dice<>,M> {
    void rollAll() {
        for(auto& x : *this) {
            x.roll();
        }
    }
};


int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);
    
    Dices<5> dices;
    
    for(int i=0; i < 3; ++i) {
        for(const auto& x : dices) {
            std::cout << x.number() << " ";
        }
        std::cout << "\n";
        dices.rollAll();
    }
    return 0;
}

5 2 3 5 1
5 6 3 1 3
2 6 5 5 2

Powered by WordPress