C++Guns – RoboBlog

05.08.2018

C++ Guns: semantics is important

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

Einfach N Zufallszahlen ziehen ist einfach. Eigentlich gibt es darüber nicht viel zu sagen, aber es ist ein schönes Beispiel für Semantik. (Dank an Ben für die Inspiration).

Wir müssen immer den Spagat zwischen Lesbarkeit und Performance machen. Manchmal sind die Verantwortlichkeiten der Funktionen nicht klar zu definieren. Soll die Funktion, welche N Zufallszahlen zieht, auch die Container erstellen in denen sie gespeichert werden? Das wäre für die Lesbarkeit förderlich, aber für die Performance schlecht. Da bei jedem Aufruf ein neuer Container initialisiert werden muss. Und wenn die Funktion einen bereits initialisierten Container übergeben bekommt und auch auf diesen Weg die Daten zurück gegeben werden, wäre der Lesefluss gestört. Vor allem muss beim ersten Aufruf von Hand ein neuer Container initialisiert werden.

Für beide Varianten gilt: Keine von Hand geschriebenen Schleifen. Sie enthalten einfach unnötige potentielle Fehler. Ein einfacher std::for_each Aufruf ist besser.
Hier mein Versuch beide Welten abzudecken. Durch die fehlenden Concepts in C++17 bläht sich der Template Code etwas auf. Aber dieses Problem löst sich ja von alleine.

// 7 loc to just call for_each... semantics is important
template<typename Container, typename PRNG, typename Distribution>
auto drawSamples(Container& data, PRNG& r, Distribution& U) {
    static_assert(std::is_arithmetic_v<typename Container::value_type>);
    static_assert(std::is_invocable_v<PRNG>, "PRNG is not invocable");
    static_assert(std::is_invocable_v<Distribution, PRNG&>, "Distribution is not invocable with PRNG");
    static_assert(std::is_same_v<typename Container::value_type, typename Distribution::result_type >, "Return type of the distribution and value type of container are not the same");

    std::for_each(std::begin(data), std::end(data), [&](auto& x){x = U(r);} );
    return data; // good idea? yes. consistent with the other drawSamples overload
                 // NO! bad feeling. pass by reference and return by value - it must be a copy somewhere. even with return optimazion.
}

template<typename PRNG, typename Distribution>
auto drawSamples(int N, PRNG& r, Distribution& U) {
    static_assert(std::is_invocable_v<PRNG>, "PRNG is not invocable");
    static_assert(std::is_invocable_v<Distribution, PRNG&>, "Distribution is not invocable with PRNG");    

    std::vector<typename Distribution::result_type> data(N);
    drawSamples(data, r, U);
    return data;
}


auto func() {
    std::minstd_rand r;
    std::uniform_int_distribution<int> U(1, 6);
    std::vector data = drawSamples(10, r, U);
    data = drawSamples(data, r, U);
    return data;
}

Bin gespannt ob sich dies in der Praxis bewährt.

update:
Ich glaube pass by reference und return by value ist immer eine Kopie. Und damit ineffizient. Selbst mit return Optimierung. Bin mir aber nicht sicher.
Vielleicht sieht das ganze mit Iteratoren und Ranges besser aus.

No Comments

No comments yet.

RSS feed for comments on this post.

Sorry, the comment form is closed at this time.

Powered by WordPress