C++Guns – RoboBlog blogging the bot

22.02.2019

C++ Guns: rvalue referenz, forwarding referenz, universal referenz WTF?

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

Die gute Nachricht: Normale Menschen müssen das alles nicht verstehen. Auch Anwendungs- Programmierer und library designer brauchen die Details nicht zu wissen. Ihr könnt meine Regeln durchlesen, daran halten und dann STOP.
Die Details sind für Leute, die sich einem 1.5h Vortrag über && anhören können.

Streng genommen wird der Begriff "universal referenz" nicht im C++ std benutzt. Dies ist ein Begriff von Scott Meyers, der ihn benutzt um die Ding begreiflich zu machen. Ich stimme mit ihm überein und benutze in diesem Text nicht den Begriff "forwared referenz" sondern "universal referenz". Viele sagen C++ sei so schon kompliziert genug. Man muss es ja nicht noch schlimmer machen. Weiterhin werde ich Abkürzungen benutzen. rvalue referenz Rref. universal referenz Uref.

Nun, Rref und Uref tauchen (auch) bei ownership transfer auf. Das ist IMO die einzige Stelle bei der man das braucht. Beispiele hierzu in C+ Guns: ACPL ownership transfer.
Nun sehen sich Rref und Uref von der Syntax her ziemlich gleich. Beide haben einen Typ und dann das doppele Kaufmanns-Und, Et-Zeichen &&. Nicht zu verwechseln mit dem logischen AND Operator. Aber für diesen nutzt ihr selbstverständlich and und nicht &&.
Es kommt nun auf den Typ an. Wird er vom Compiler hergeleitet (deduction) und ist es GENAU (im Sinne von EXACT) die Form T&&, dann ist es Uref, sonst Rref. Der Typ T ist ein Template Parameter und sein Name natürlich frei wählbar. Aber sobald ein const hinzugefügt wird, verschwindet das Uref und das Rref taucht auf.

Im ersten Beispiel (in dem verlinkten Artikel) ist somit Rref am Werk. Im zweiten Beispiel Uref. Und im dritten garnichts, weil man von const Variablen nicht moven kann!

Funktionen überladen mit Rref und Uref ist so eine Sache. Uref ist eine _*UNIVERSAL*_!!! Referenz. Sie behandelt ALLES. Und damit ist exakt alles gemeint. Nun ist es technisch (Stand 02.2019 pre C++20) möglich, eine Funktion zu überladen, so dass die eine Rref und die andere Uref akzeptiert. Aber das macht keinen Sinn und ist meiner Meinung nach ein Compilezeit Fehler.

Funktionen mit Rref und "normalen" Referenzen zu überladen ist natürlich möglich, gewollt und hilfreich. Möchte man mit den einen doch etwas anderes machen als mit den anderen (move vs. read only). Und da sich Rref und Uref ähnlich sehen, viel Spass!

Jetzt wird aus jedem Rref und Uref wieder ein ganz normales lvalue. Weil die haben einen Variablennamen und eine Adresse. Zwangsläufig, sonst könnte man den Variablennamen beim programmieren ja nicht eintippen -_-
Also muss wann immer eine Rref oder Uref Variable weiter benutzt wird, ein std::move oder std::forward kommen. Für Rref, also move Operationen nutzen wir std::move. Für Uref, also move oder copy Operationen nutzen wird std::forward. Benutzen wir keines von beiden, ist das meiner Meinung nach ein Compilezeit Fehler. Jemand sollte ein GCC PLugin schreiben.

Regeln:

  • Uref und Rref braucht man (unter anderem) für ownership transfer. Ihr werde es kaum brauchen.
  • Uref genau wenn T ein template parameter und exact die Form T&& vorliegt, ohne const ö.ä, sonst Rref.
  • T&& verhält sich in der Praxis NICHT wie Rref. Es kommt auf T an.
  • Wenn immer T&& im Code auftaucht: Gelber ALARM! Braucht man das wirklich? Wird überall std::move() oder std::forward() angewandt?
  • Von const Variablen kann man nicht moven.
  • Funktionen überladen mit Uref -> Fehler
  • Rref und normale Referenzen überladen -> Gut
  • Rref -> std::move
  • Uref -> std::forward
  • Soll ownership tranfer UND eine Kopie möglich sein, macht die Kopie explizit im Code deutlich:
  •  // v ist KEINE uref, da kein template Parameter
    auto func(std::vector && v) 
    { }
    
    std::vector v;
    func(acpl::moveOwnership(v)); // ownership transfer
    func(std::vector(v)); // Kopie
    

    STOP

    Für weitere Informationen zu auto&&, decltype(x), decltype((x)) und sonstige Schmerzen schau euch den Vortrag von Scott Meyers an. Er ist wirklich gut!
    C++ and Beyond 2012: Scott Meyers - Universal References in C++11

    C+ Guns: ACPL ownership transfer

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

    acpl::moveOwnership bietet im Gegensatz zu std::move den Vorteil, dass ein move-from const Variablen einen Fehler zur Compilezeit erzeugt.

    Zwei Beispiele sollen ownership transfer verdeutlichen.
    Daten in ein Type moven und dort speichern.

    #include <core/util/functional.hpp>
    
    struct Node {};
    struct Edge {};
    struct Graph {
        std::vector<Node> nodes;
        std::vector<Edge> edges;
    
        Graph(std::vector<Node>&& nodes, std::vector<Edge>&& edges) 
        : nodes(acpl::moveOwnership(nodes)), edges(acpl::moveOwnership(edges))
        {
        }
    };
    
    void func() {
        std::vector<Node> nodes;
        std::vector<Edge> edges;
        // ... fill nodes and edges
        // graph is the new ownership of nodes and data
        Graph graph(acpl::moveOwnership(nodes), acpl::moveOwnership(edges));
        assert(nodes.empty());
        assert(edges.empty());
    }
    

    Daten entweder kopieren oder durch eine Funktion moven.

    #include <core/util/functional.hpp>
    
    template<typename Container>
    inline std::vector<int> copyOrMove(Container&& x) {
        Container data2(std::forward<Container>(x));
        return data2;
    }
    
    void func() {
        std::vector<int> x {1,2,3};
        std::vector<int> copy = copyOrMove(x);
        assert(not x.empty());    
        std::vector<int> moved = copyOrMove(acpl::moveOwnership(x));
        assert(x.empty());    
    }
    

    Move von const Variablen ist nicht möglich, da die Variablen geändert werden würden, was nicht möglich ist, weil sie const sind.

    void func() {
        const std::vector<int> x {1,2,3};
        std::vector<int> moved = acpl::moveOwnership(x); // error: static assertion failed: Move from const variables dosent make sense
    }
    

    15.02.2019

    C++ Guns: class template specialization with concepts aka C++20 std::ranges::value_type

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

    You can create a constrain for a class. And another constrain for the same class. Isn't this crazy stuff?

    With this we can build something like an identity function for types, like std::type_identity, for value_type. It's something like the C++17 non-member functions size() and empty(). Why must value_type be a member? It can be a non-member trait.

    It turns out, there is already this functionality in C++ 20 ranges TS in std::experimental::ranges::value_type. It's really hard to catch up... but implemented it for you. And users may specialize value_type I also put my part for arithmetic types here.

    This is part of ACPL functional.hpp file.

    /// Primary template is an empty struct.
    /// \note this is in C++20 ranges now except for the arithmetic types overload 
    /// https://en.cppreference.com/w/cpp/experimental/ranges/iterator/value_type
    template<class I>
    struct value_type { };
    
    /// Specialization for pointers.
    /// If T is an object type, provides a member type type equal to std::remove_cv_t<T>.
    ///  Otherwise, there is no member type.
    template<class T>
    struct value_type<T*> {
      using type = std::remove_cv_t<T>;
    };
    
    /// Specialization for array types.
    template<class I>
      requires(std::is_array<I>::value)
    struct value_type<I> : value_type<std::decay_t<I>> { };
    
    /// Specialization for const-qualified types.
    template<class T>
    struct value_type<const T> : value_type<std::decay_t<T>> { };
    
    /// Specialization for types that define a public and accessible member type value_type.
    /// If T::value_type is an object type, provides a member type type equal to T::value_type.
    /// Otherwise, there is no member type.
    /// \todo requires requires
    template<class T>
      requires requires{ typename T::value_type; }
    struct value_type<T> {
      using type = typename T::value_type;
    };
    
    /// Specialization for types that define a public and accessible member type element_type.
    /// (e.g., std::shared_ptr).
    /// If T::element_type is an object type, provides a member type type equal to std::remove_cv_t<typename T::element_type>.
    /// Otherwise, there is no member type.
    template<class T>
      requires requires{ typename T::element_type; }
    struct value_type<T> {
        using type = typename T::element_type;
    };
    
    /// Helper alias template
    template<class T>
    using value_type_t = typename value_type<T>::type;
    
    /// ACPL specialization for arithmetic types
    template<class T>
      requires(std::is_arithmetic_v<T>)
    struct value_type<T> {
        using type = T;
    };
    
        // Specialization for pointers.
        static_assert(std::is_same_v<int, acpl::value_type_t<int*>>);
        // Specialization for array types.
        static_assert(std::is_same_v<int, acpl::value_type_t<int[]>>);
        // Specialization for const-qualified types.
        static_assert(std::is_same_v<int, acpl::value_type_t<const int*>>);
        // Specialization for types that define a public and accessible member type value_type.
        static_assert(std::is_same_v<int, acpl::value_type_t<std::array<int,1>>>);
        // Specialization for types that define a public and accessible member type element_type.
        static_assert(std::is_same_v<int, acpl::value_type_t<std::unique_ptr<int>>>);
    
        // ACPL specialization for arithmetic types
        static_assert(std::is_same_v<int, acpl::value_type_t<int>>);
        static_assert(std::is_same_v<int, acpl::value_type_t<const int>>);
    

    For the record, this is my first try:

    template<typename T>
    struct value_type {
    };
    
    template<typename T>
    requires(std::is_scalar_v<T>)
    struct value_type<T> {
        using type = T;
    };
    
    template<typename T>
    requires(not std::is_scalar_v<T>)
    struct value_type<T> {
        using type = typename T::value_type;
    };
    
    template<typename T>
    using value_type_t = typename value_type<T>::type;
    
    static_assert(std::is_same_v<int, value_type_t<int>> );
    static_assert(std::is_same_v<int, value_type_t<std::vector<int>>> );
    

    03.02.2019

    C++ Guns: ACPL proudly presents: BinaryHeap

    Filed under: Allgemein — Thomas @ 21:02

    See also
    ACPL: Histogram1D
    ACPL: Histogram2D

    BinaryHeap as array implemente priority queue. Good for way finding graph algorithmen like dijkstra.

    See source code and more code examples at ACPL Binary Heap

    #include <core/util/StringStream.hpp>
    #include <core/util/BinaeryHeap.hpp>
    
    using namespace acpl;
    using ID = int;
    
    int main() {
        // We use a min-heap. The highest priority is the lowest number
        BinaryHeap<double> heap;
        std::map<ID, std::string_view> tasks;
    
        // heap insert returns a ID assiciated with the priority
        tasks.insert({heap.insert(3.0),  "clear drains"});
        tasks.insert({heap.insert(4.0),  "feet cat"});
        tasks.insert({heap.insert(5.0),  "make tea"});
        tasks.insert({heap.insert(1.0),  "solve RC tasks"});
        tasks.insert({heap.insert(2.0),  "tax return"});
        tasks.insert({heap.insert(10.0), "clean room"});
        tasks.insert({heap.insert(99.0), "wash dog"});
    
        std::cout << heap.size() << " tasks" << newline;
    
        std::cout << "Most important: ";
        auto firstToDo = heap.extract_minimum();
        std::cout << tasks[firstToDo.second] << " with priority " << firstToDo.first << newline;
        std::cout << heap.size() << " tasks left" << newline;
    
        std::cout << "\nNext 3 tasks:" << newline;
        for(int i=0; i < 3; i++) {
            auto task = heap.extract_minimum();
            std::cout << tasks[task.second] << " with priority " << task.first << newline;
        }
    
        std::cout <<"make task 'wash the dog' the new first with priority -1" << newline;
        ID lastInsertedID = tasks.rbegin()->first;
        heap.decrease_key(lastInsertedID, -1);
    
        std::cout << "\nThe remaining tasks:" << newline;
        while(not heap.empty()) {
            auto task = heap.extract_minimum();
            std::cout << tasks[task.second] << " with priority " << task.first << newline;
        }
    
        std::cout << heap.size() << " tasks left" << newline;
    }
    
    7 tasks
    Most important: solve RC tasks with priority 1
    6 tasks left
    
    Next 3 tasks:
    tax return with priority 2
    clear drains with priority 3
    feet cat with priority 4
    make task 'wash the dog' the new first with priority -1
    
    The remaining tasks:
    wash dog with priority -1
    make tea with priority 5
    clean room with priority 10
    0 tasks left
    

    C++ Guns: ACPL proudly presents: Histogram2D

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

    See also
    ACPL: Histogram1D
    ACPL: BinaryHeap

    Create even 2D Histogram in a fast and intuitive way.
    Define TWO access functions, Axis, Range, Titles, get stochastic moments for every dimension. Enjoy the 2D ASCII art output.
    See source code and more code examples at ACPL Histogram 2D

    std::vector<std::pair<double,double>> values;
    auto temp     = [](const std::pair<double, double>& p){ return p.first; };
    auto pressure = [](const std::pair<double, double>& p){ return p.second; };
    
    HistogramAxis Xaxis("temp [degree]",       FixBinWidth{0.5}, DataIntervalClosed{-4.0,7.0});
    HistogramAxis Yaxis("air pressure [hPa]",  FixBinSize{20},   makeDataIntervalClosed(values, pressure));
    Histogram2D hist = Histogram2D("City temperature VS air pressure",  Xaxis, temp, Yaxis, pressure, values);
    std::cout << hist;
    
    City temperature VS air pressure
    Axis: temp [degree]
    Number of bins: 22 bin width 0.5
    Under/ Overflow count: 4 14
               min    max   avg    var    std 
    Histogram -4 7 3.56703 6.43098 2.53594
    Data      -4.5 7.6 3.62 7.27079 2.69644  
    Axis: air pressure [hPa]
    Number of bins: 20 bin width 1.2
    Under/ Overflow count: 0 0
               min    max   avg    var    std 
    Histogram 1014 1038 1030.45 40.2583 6.34494
    Data      1014 1038 1030.45 40.2583 6.34494
          1038 |
             a |
             i |
             r |
               |
             p |
             r |
             e |
             s |
             s |
             u |
             r |
             e |
               |
             [ |
             h |
             P |
             a |
             ] |
          1014 |
               +-------------------------
                -4                 7
                  temp [degree]
    

    C++ Guns: ACPL proudly presents: Histogram1D

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

    See also
    ACPL: Histogram2D
    ACPL: BinaryHeap

    Create 1D Histogram in a fast and intuitive way.
    Define an Axis, Range, Title, get stochastic moments. Enjoy the ASCII art output.
    See source code and more code examples at ACPL Histogram 1D

    std::vector<double> tempValues;
    HistogramAxis axis("temp [degree]", FixBinSize{50}, makeDataIntervalClosed(tempValues));
    Histogram hist = Histogram("City temperature", axis, tempValues);
    cout << hist;
    
    City temperature
    Axis: temp [degree]
    Number of bins: 50 bin width 0.242
    Under/ Overflow count: 0 0
    min max avg var std
    Histogram -4.5 7.6 3.62 7.27079 2.69644
    Data -4.5 7.6 3.62 7.27079 2.69644
    

    Powered by WordPress