A fully templated version was not so hard after all, so here it is in a separate answer, again with live example. If I'm not mistaken, this should have zero overhead on top of custom nested loops. You could measure and let me know. I intend to implement this for my own purposes anyway, that's why I put this effort here.
template<size_t N>
using size = std::integral_constant<size_t, N>;
template<typename T, size_t N>
class counter : std::array<T, N>
{
using A = std::array<T, N>;
A b, e;
template<size_t I = 0>
void inc(size<I> = size<I>())
{
if (++_<I>() != std::get<I>(e))
return;
_<I>() = std::get<I>(b);
inc(size<I+1>());
}
void inc(size<N-1>) { ++_<N-1>(); }
public:
counter(const A& b, const A& e) : A(b), b(b), e(e) { }
counter& operator++() { return inc(), *this; }
operator bool() const { return _<N-1>() != std::get<N-1>(e); }
template<size_t I>
T& _() { return std::get <I>(*this); }
template<size_t I>
constexpr const T& _() const { return std::get <I>(*this); }
};
Instead of operator[]
I now have method _
(feel free to rename), which is just a shortcut for std::get
, so usage is not so much more verbose than with operator[]
:
for (counter<int, N> c(begin, end); c; ++c)
cout << c._<0>() << " " << c._<1>() << " " << c._<2>() << endl;
In fact, you may try the previous version
for (counter<int, N> c(begin, end); c; ++c)
cout << c[0] << " " << c[1] << " " << c[2] << endl;
and measure, because it may be equivalent. For this to work, switch std::array
inheritance to public
or declare using A::operator[];
in counter
's public
section.
What is definitely different is operator++
, which is now based on recursive template function inc()
and the problematic condition if (n < N - 1)
is replaced by a specialization (actually, overload) that has no overhead.
If it turns out that there is overhead eventually, an ultimate attempt would be to replace std::array
by std::tuple
. In this case, std::get
is the only way; there is no operator[]
alternative. It will also be weird that type T
is repeated N
times. But I hope this won't be needed.
Further generalizations are possible, e.g. specifying a (compile-time) increment step per dimension or even specifying arbitrary indirect arrays per dimension, e.g. to simulate
a([3 5 0 -2 7], -4:2:20)
in Matlab-like syntax.
But this needs even more work, and I think you can take it on from here if you like the approach.