Clean ways to write multiple 'for' loops

For an array with multiple dimensions, we usually need to write a for loop for each of its dimensions. For example:

vector< vector< vector<int> > > A;

for (int k=0; k<A.size(); k++)
{
    for (int i=0; i<A[k].size(); i++)
    {
        for (int j=0; j<A[k][i].size(); j++)
        {
            do_something_on_A(A[k][i][j]);
        }
    }
}

double B[10][8][5];
for (int k=0; k<10; k++)
{
    for (int i=0; i<8; i++)
    {
        for (int j=0; j<5; j++)
        {
            do_something_on_B(B[k][i][j]);
        }
    }
}

You see this kind of for-for-for loops in our code frequently. How do I use macros to define the for-for-for loops so that I don't need to re-write this kind of code every time? Is there a better way to do this?


Solution 1:

The first thing is that you don't use such a data structure. If you need a three dimensional matrix, you define one:

class Matrix3D
{
    int x;
    int y;
    int z;
    std::vector<int> myData;
public:
    //  ...
    int& operator()( int i, int j, int k )
    {
        return myData[ ((i * y) + j) * z + k ];
    }
};

Or if you want to index using [][][], you need an operator[] which returns a proxy.

Once you've done this, if you find that you constantly have to iterate as you've presented, you expose an iterator which will support it:

class Matrix3D
{
    //  as above...
    typedef std::vector<int>::iterator iterator;
    iterator begin() { return myData.begin(); }
    iterator end()   { return myData.end();   }
};

Then you just write:

for ( Matrix3D::iterator iter = m.begin(); iter != m.end(); ++ iter ) {
    //  ...
}

(or just:

for ( auto& elem: m ) {
}

if you have C++11.)

And if you need the three indexes during such iterations, it's possible to create an iterator which exposes them:

class Matrix3D
{
    //  ...
    class iterator : private std::vector<int>::iterator
    {
        Matrix3D const* owner;
    public:
        iterator( Matrix3D const* owner,
                  std::vector<int>::iterator iter )
            : std::vector<int>::iterator( iter )
            , owner( owner )
        {
        }
        using std::vector<int>::iterator::operator++;
        //  and so on for all of the iterator operations...
        int i() const
        {
            ((*this) -  owner->myData.begin()) / (owner->y * owner->z);
        }
        //  ...
    };
};

Solution 2:

Using a macro to hide the for loops can be a lot confusing, just to save few characters. I'd use range-for loops instead:

for (auto& k : A)
    for (auto& i : k)
        for (auto& j : i)
            do_something_on_A(j);

Of course you can replace auto& with const auto& if you are, in fact, not modifying the data.

Solution 3:

Something like this can help:

 template <typename Container, typename Function>
 void for_each3d(const Container &container, Function function)
 {
     for (const auto &i: container)
         for (const auto &j: i)
             for (const auto &k: j)
                 function(k);
 }

 int main()
 {
     vector< vector< vector<int> > > A;     
     for_each3d(A, [](int i){ std::cout << i << std::endl; });

     double B[10][8][5] = { /* ... */ };
     for_each3d(B, [](double i){ std::cout << i << std::endl; });
 }

In order to make it N-ary we need some template magic. First of all we should create SFINAE structure to distinguish whether this value or container. The default implementation for values, and specialisations for arrays and each of the container types. How @Zeta notes, we can determine the standard containers by the nested iterator type (ideally we should check whether the type can be used with range-base for or not).

 template <typename T>
 struct has_iterator
 {
     template <typename C>
     constexpr static std::true_type test(typename C::iterator *);

     template <typename>
     constexpr static std::false_type test(...);

     constexpr static bool value = std::is_same<
         std::true_type, decltype(test<typename std::remove_reference<T>::type>(0))
     >::value;
 };

 template <typename T>
 struct is_container : has_iterator<T> {};

 template <typename T>
 struct is_container<T[]> : std::true_type {};

 template <typename T, std::size_t N>
 struct is_container<T[N]> : std::true_type {}; 

 template <class... Args>
 struct is_container<std::vector<Args...>> : std::true_type {};

Implementation of for_each is straightforward. The default function will call function:

 template <typename Value, typename Function>
 typename std::enable_if<!is_container<Value>::value, void>::type
 rfor_each(const Value &value, Function function)
 {
     function(value);
 }

And the specialisation will call itself recursively:

 template <typename Container, typename Function>
 typename std::enable_if<is_container<Container>::value, void>::type
 rfor_each(const Container &container, Function function)
 {
     for (const auto &i: container)
         rfor_each(i, function);
 }

And voila:

 int main()
 {
     using namespace std;
     vector< vector< vector<int> > > A;
     A.resize(3, vector<vector<int> >(3, vector<int>(3, 5)));
     rfor_each(A, [](int i){ std::cout << i << ", "; });
     // 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,

     std::cout << std::endl;
     double B[3][3] = { { 1. } };
     rfor_each(B, [](double i){ std::cout << i << ", "; });
     // 1, 0, 0, 0, 0, 0, 0, 0, 0,
 }

Also this will not work for pointers (arrays allocated in heap).

Solution 4:

Most of the answers simply demonstrate how C++ can be twisted into incomprehensible syntactic extensions, IMHO.

By defining whatever templates or macros, you just force other programmers to understand bits of obfuscated code designed to hide other bits of obfuscated code.
You will force every guy who reads your code to have template expertise, just to avoid doing your job of defining objects with clear semantics.

If you decided to use raw data like 3 dimensional arrays, just live with it, or else define a class that gives some understandable meaning to your data.

for (auto& k : A)
for (auto& i : k)
for (auto& current_A : i)
    do_something_on_A(current_A);

is just consistent with the cryptic definition of a vector of vector of vector of int with no explicit semantics.