Spielerei mit Integer-Range-based for loop

Design Patterns, Erklärungen zu Algorithmen, Optimierung, Softwarearchitektur
Forumsregeln
Wenn das Problem mit einer Programmiersprache direkt zusammenhängt, bitte HIER posten.
Antworten
DerAlbi
Establishment
Beiträge: 269
Registriert: 20.05.2011, 05:37

Spielerei mit Integer-Range-based for loop

Beitrag von DerAlbi »

Gibts sicher schon oft, aber war auch nur ein kleiner Test damit man in C++17 fitter bleibt.
Interessant (nicht zwingend hübsch) finde ich auch den alternativen From().To()-Syntax. Und Sry, ich weiß, dass Syntax weiblich ist, ist es in meinem Kopf aber nicht. Bei mir ist immer "der Syntax". Geht das anderen auch so?

Code: Alles auswählen

#include <type_traits>

template <typename T> class value_range
{
public:
        struct iterator
        {
            T value;
            const T step;
            constexpr iterator    (T _value, T _step = T(1)) : value(_value), step(_step) {}
            constexpr operator T  () const    { return value; }
            constexpr operator T& ()          { return value; }
            constexpr T operator* () const    { return value; }
            constexpr iterator& operator++ () { value += step; return *this; }
            constexpr bool operator != (const iterator& other) const {return (step>0) ? value < other.value : value > other.value;}
        };
        constexpr value_range(const T _end): i_begin(T(0)), i_end(_end) {}
        constexpr value_range(const auto _start, const auto _end): i_begin(_start), i_end(_end) {}
        constexpr value_range(const auto _start, const auto _end, const auto _step): i_begin(_start, _step), i_end(_end, _step) {}

        constexpr const iterator& begin() const { return i_begin; }
        constexpr const iterator& end() const { return i_end; }
        using value_type = T;
private:
        const iterator i_begin;
        const iterator i_end;
};

template<typename T1, typename T2> value_range(T1, T2)                  -> value_range<std::common_type_t<T1, T2>>;
template<typename T1, typename T2, typename T3> value_range(T1, T2, T3) -> value_range<std::common_type_t<T1, T2>>;    

//=================================================================================================================================

template<typename T>struct To
{
    const value_range<T> i;
    To(T end) : i(end) {};
    constexpr const typename value_range<T>::iterator& begin() const { return i.begin(); }
    constexpr const typename value_range<T>::iterator& end() const { return i.end(); }
};

template<typename T> auto From(const T start)
{
    struct ProvideTo
    {   
        const T start;
        constexpr ProvideTo(const T _start): start(_start) {}
        constexpr auto To(T _end) const
        {
            struct ProvideStep
            {
                const value_range<T> i;
                constexpr ProvideStep(T _start, T _end): i(_start, _end) {};
                constexpr const typename value_range<T>::iterator& begin() const { return i.begin(); }
                constexpr const typename value_range<T>::iterator& end() const { return i.end(); }
                constexpr auto Step(T _step)
                {
                    return value_range{*i.begin(), *i.end(), _step};
                }
            };
            return ProvideStep{start, _end};
        }
    };
    return ProvideTo{start};
};

//=================================================================================================================================

#include <numeric>

int square(int num)
{
    int sum = 0;    
    
    for(auto i : To(5)) sum += i;                   //0+1+2+3+4     = 10
    for(auto i : From(1).To(5)) sum += i;           //1+2+3+4       = 10
    for(auto i : From(1).To(5).Step(2)) sum += i;   //1+3           =  4
    for(auto i : value_range(5)) sum += i;          //0+1+2+3+4     = 10
    for(auto i : value_range{3, 5u}) sum += i;      //3+4           =  7
    for(auto i : value_range{3, 6u, 2.0}) sum += i; // 3+5          =  8
    //                                                                49
    auto range = value_range(1.0f, 10.0f, 1);
    auto fsum = std::accumulate(range.begin(), range.end(), 0.0);
    asm volatile ("":"=m"(fsum):"m"(fsum));

    return sum; 
}
//Höhö Highlighting kaputt:
constexpr
operator
override
final
asm
Ob das constexpr überall sein muss.. kA.
Hier https://godbolt.org/g/3BF2SP
kA, nix Wildes, aber irgendwie finde ich es teilungswürdig. Vielleicht gibts ja Kommentare :-)
Benutzeravatar
xq
Establishment
Beiträge: 1581
Registriert: 07.10.2012, 14:56
Alter Benutzername: MasterQ32
Echter Name: Felix Queißner
Wohnort: Stuttgart & Region
Kontaktdaten:

Re: Spielerei mit Integer-Range-based for loop

Beitrag von xq »

Cool!

Aber eine Sachen:
Wenn ich einem Kind sage: "Zähle von 1 bis 5", wird mir das "1, 2, 3, 4, 5" sagen und ich denke, das ist auch die "natürliche" Aussage dahinter und ich würde erwarten, dass das Programm das auch macht.

Was man da noch als Alternative, bzw. Zusätze dafür machen könnte:

Code: Alles auswählen

for(auto i : Zero.TakeNext(3)) // 0, 1, 2
for(auto i : From(4).TakeNext(3)) // 4, 5, 6

for(auto i : Zero.To(3)) // 0, 1, 2, 3
for(auto i : From(4).To(8)) // 4, 5, 6, 7, 8

// Infinity oder Max
for(auto i : From(100).To(Infinity)) // 100, 101, 102, ............, 2000000000, 2000000001, 2000000002, ....., 2147483647, std::overflow_error
Aber coole Sache!
War mal MasterQ32, findet den Namen aber mittlerweile ziemlich albern…

Programmiert viel in ⚡️Zig⚡️ und nervt Leute damit.
DerAlbi
Establishment
Beiträge: 269
Registriert: 20.05.2011, 05:37

Re: Spielerei mit Integer-Range-based for loop

Beitrag von DerAlbi »

Dein Infinity bekommst du z.B. durch value_range(0, INT_MAX). Aber eine extra Klasse dafür wäre wohl angebracht.

Bezüglich des Wie-weit-zählens habe ich das selbe Gefühl, wie du.. zufrieden bin ich nicht. Man müsste klarmachen, dass es inclusive oder exclusive ist aber sowas fügt wieder unsinnige Komplexität hinzu. Weicht man von der Intuition ab, oder behält man die Zahlen bei, die sonst in einer regulären for-Schleife drin wären? määh
Man könnte die Klasse auch in half_open_range umbenennen. (was das Problem beim From().To() nicht löst - ist also eine insgesamt schlechte Idee solch einen Syntax zu haben)

Ich weiß auch nicht, ob die deduction-guides so sinnvoll sind. Ehrlich gesagt verliert man z.B. den Überblick, was bei decltype(value_range{0u, 5.0f, 1})::value_type rauskommt. Aber eine For-Schleife mit solch unsinnigen Werten ist natürlich auch nicht sonderlich schön. Ich wollte hauptsächlich -Wconversion-Warnings unterdrücken, falls man int und short-Grenzen (oder signedness) mixt und so.

Im obigen Code von mir braucht der Typ T auch beide Vergleichopteratoren "<" und ">". Das ist dumm. Man sollte sich auf "<" beschränken und im anderen Fall die Operanden tauschen. [falls man rückwärtszählen will)
Antworten