Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to convert this "for i" c++ preprocessor macro to modern C++ (11+)?

I'm looking for a way to replace this C++ preprocessor macro with something more modern.

#define fori(FORI_TYPE, FORI_FROM, FORI_TO) \
            for(FORI_TYPE i{FORI_FROM}; \
            ((FORI_FROM) < (FORI_TO)) ? (i < (FORI_TO)) : (i > (FORI_TO)); \
            ((FORI_FROM) < (FORI_TO)) ? ++i : --i )

Ideally, I would be able to get rid of all the ? operators (can constexpr be useful here?) and have "fori" not incur any overhead cost like it does now with the proceprocessor version (evaluations of ? operators). Also, type safety.

Usage example:

fori(size_t, 0, n)
{
    cout << i << endl;
}
like image 973
binarez Avatar asked Mar 05 '23 06:03

binarez


1 Answers

For what it's worth, you're always going to need to know which direction to iterate, so you can't get rid of that overhead. That said, by switching away from macros, you can at least make it easier to optimise (partially by making the inputs const to promote folding of duplicate/similar conditions on them, and partially by precalculating a "step" distance to eliminate some of those conditions entirely).

As far as macros go, that one isn't too bad (though it could probably use a () or two…).

The truly "modern" thing to do would be to use a counting iterator, or something related to irange.

For example, naively adapting Neil's code to provide automatic step direction detection:

#include <iostream>

template <class T>
class range
{
private:
    class iter
    {
    private:
        T at, step;
    public:
        iter(T at, T step) : at(at), step(step) {}
        bool operator!=(iter const& other) const { return at != other.at; }
        T const& operator*() const { return at; }
        iter& operator++() { at += step; return *this; }
    };

    T begin_val;
    T end_val;
    T step_val;

public:

    range(const T begin_val, const T end_val)
        : begin_val(begin_val)
        , end_val(end_val)
        , step_val(begin_val > end_val ? -1 : 1)
    {}

    iter begin() { return iter(begin_val, step_val); }
    iter end() { return iter(end_val, step_val); }
};

int main()
{
   for (auto i : range<unsigned>(42, 10))
      std::cout << i << ' ';
   std::cout << '\n';
}

// Output: 42 41 40 39 38 37 36 35 34 33 32 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 

(live demo)

Or, to be quite frank, you could just write the following and be done with it:

#include <iostream>

int main()
{
   for (unsigned int i = 42; i > 10; --i)
      std::cout << i << ' ';
   std::cout << '\n';
}

// Output: 42 41 40 39 38 37 36 35 34 33 32 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 

(live demo)

Everybody's going to understand that; no tricks required.

In either case, and despite my examples above, I'd actually advise not using unsigned types for this.

like image 131
Lightness Races in Orbit Avatar answered Apr 28 '23 23:04

Lightness Races in Orbit