Artikel: C++ – From goto to std::transform



  • Ich sehe das ähnlich. Die Argumentation, dass

    vector<int> result;
        result.reserve(v.size());
        transform(begin(v), end(v), back_inserter(result), [](int i)
        {
            return i*i;
        });
        return result;
    

    besser oder sogar lesbarer als

    for (int& i : v)
        {
            i = i * i;
        }
        return v;
    

    ist, weil wir anhand des Namens "transform" die Semantik erkennen, ist nicht überzeugend. Vor allem, da wir erheblich mehr Code haben, und ein Grossteil davon syntaktischer Boilerplate ist.



  • Nexus schrieb:

    Ich sehe das ähnlich. Die Argumentation, dass snip besser oder sogar lesbarer als snip ist, weil wir anhand des Namens "transform" die Semantik erkennen, ist nicht überzeugend. Vor allem, da wir erheblich mehr Code haben, und ein Grossteil davon syntaktischer Boilerplate ist.

    Der for -Code ist besser lesbar, aber er verändert den ursprünglichen vector , während der transform -Code einen neuen erzeugt. Wenn man ihn durch eine push_back -Lösung ersetzt, wird der Zusammenhang zwischen der Reihenvolge von v und result nicht unmittelbar deutlich, während bei transform in der Hinsicht die Semantik klar ist.

    Dass der Code trotzdem nicht schön ist, liegt, wie du schon sagtest, an der Syntax, sowohl bezüglich der Iteratoren, als auch bezüglich des Lambdas. Code wie

    result = transform(v, i => i * i)
    

    wäre kürzer und lesbarer und die Semantik ist unmittelbar ersichtlich, ich würde ihn deshalb tatsächlich der Schleife vorziehen. Das Problem mit den Iteratoren kann man durch Ranges lösen, bei den Lambdas ist es in der Hinsicht schade, dass für solche Fälle C++ keine übersichtlichere Syntax bietet.


  • Mod

    bei den Lambdas ist es in der Hinsicht schade, dass für solche Fälle C++ keine übersichtlichere Syntax bietet.

    Also rein theoretisch ist es möglich, mittels Boost.Proto was zu basteln.



  • Mit Boost kann man bereits einiges machen.

    #include <boost/lambda/lambda.hpp>
    #include <boost/range/algorithm/for_each.hpp>
    #include <boost/range/algorithm/copy.hpp>
    #include <boost/range/algorithm/transform.hpp>
    #include <iostream>
    
    int main()
    {
    	using boost::lambda::_1;
    
    	std::ostream_iterator<int> out(std::cout, "\n");
    
    	std::vector<int> result{1, 2, 3, 4};
    
    	boost::for_each(result, _1 *= _1);
    	boost::copy(result, out);
    
    	boost::transform(result, out, _1 * _1);
    }
    


  • Nexus schrieb:

    Ich sehe das ähnlich. Die Argumentation, dass [...] besser oder sogar lesbarer als [...] ist, weil wir anhand des Namens "transform" die Semantik erkennen, ist nicht überzeugend. Vor allem, da wir erheblich mehr Code haben, und ein Grossteil davon syntaktischer Boilerplate ist.

    Oh, da hatte ich wohl vergessen, die std::transform-Version im Artikel auch auf in-place (schneller wegen loop vectorization) umzustellen.
    Jetzt ist es richtig:

    vector<int> squareVec5(vector<int> v)
    {
        transform(begin(v), end(v), begin(v), [](int i)
        {
            return i*i;
        });
        return v;
    }
    

    Klar ist das immernoch länger als

    vector<int> squareVec4(vector<int> v)
    {
        for (int& i : v)
        {
            i = i * i;
        }
        return v;
    }
    

    Aber ich finde, dass das bei einem längeren loop-body nicht mehr ins Gewicht fällt. Da freu ich mich dann eher, dass ich dem head direkt ansehe, was los ist.
    Ansonsten gibts ja immernoch den transformCont-Wrapper.



  • Hallo,

    der Text zum 'Range-based for loop'-Abschnitt passt denke ich nicht gut zum Code:

    really know that result is guaranteed to have the same size as v in the end.

    'result' hat einen Rahmen, was wohl sowas wie 'Code-Teil' bedeutet.
    'result' gibts aber im Code gar nicht und v hat natürlich immer die size von v.
    Mir ist klar was gemeint ist, hat mich aber erstmal verwirrt.



  • Oh, danke. Das stammte noch aus der Zeit, als ich die Transformation noch nicht in-place gemacht hab. Habs auch direkt korrigiert.



  • nwp3 schrieb:

    Gibt es einen Grund, wieso transformCond nicht transform heißt?

    Weil transform mit std::transform clashed würde wenn man using namespace std macht.

    nwp3 schrieb:

    Und fehlen da nicht die Referenzen? (in diesem speziellen Fall nicht, aber im allgemeinen Fall schon)

    Wo meinst du? Ich lass mir den Container hier für den Fall, dass es ein Vektor ist, extra kopieren.

    nwp3 schrieb:

    Bei Performance-vergleichen musst du mindestens den Compiler, die Flags, die Messmethode sowie Rechnerspecs angeben.

    Die Compiler-Flags stehen im source code oben als Kommentar, und was fürn Rechner ich hab, find ich ziemlich egal. Es geht ja nicht darum, wie schnell wie varianten absolut sind, sondern nur im Verhältnis zueinander.

    nwp3 schrieb:

    Die Messmethode hast du als Quelltext angegeben, aber ich bin nicht sofort überzeugt, dass das squareVecNTimes mit dem function-Object nichts verfälscht.

    Naja, der vector hat 10000 Elemente. Es wird also 10000 mal so oft ein int quadriert, wie das std::function-Objekt gecallt wird. Da ist der overhead also vollkommen irrelevant.

    nwp3 schrieb:

    Ein Vergleich des erzeugten Assemblercodes wäre bestimmt sinnvoller als eine Zeitmessung.

    Ja, das hab ich auch probiert, aber keinen Durchblick bekommen. 😕


  • Mod

    @Tyrox: Vielen Dank, kannte ich nicht.

    Gibt es vielleicht eine Liste mit den geilsten Boost-Funktionen? Boost.Lambda ist ja eine semantische Goldgrube.



  • Dobi schrieb:

    nwp3 schrieb:

    Und fehlen da nicht die Referenzen? (in diesem speziellen Fall nicht, aber im allgemeinen Fall schon)

    Wo meinst du? Ich lass mir den Container hier für den Fall, dass es ein Vektor ist, extra kopieren.

    Ich denke eher, dass er mit Referenzen die Zitation und Quellen meint.


Anmelden zum Antworten