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;
}
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
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With