For an array with multiple dimensions, we usually need to write a for
loop for each of its dimensions. For example:
vector< vector< vector
I see many answers here that work recursively, detecting if the input is a container or not. Instead, why not detect if the current layer is the same type as the function takes? It's far simpler, and allows for more powerful functions:
//This is roughly what we want for values
template<class input_type, class func_type>
void rfor_each(input_type&& input, func_type&& func)
{ func(input);}
//This is roughly what we want for containers
template<class input_type, class func_type>
void rfor_each(input_type&& input, func_type&& func)
{ for(auto&& i : input) rfor_each(i, func);}
However, this (obviously) gives us ambiguity errors. So we use SFINAE to detect if the current input fits in the function or not
//Compiler knows to only use this if it can pass input to func
template<class input_type, class func_type>
auto rfor_each(input_type&& input, func_type&& func) ->decltype(func(input))
{ return func(input);}
//Otherwise, it always uses this one
template<class input_type, class func_type>
void rfor_each(input_type&& input, func_type&& func)
{ for(auto&& i : input) rfor_each(i, func);}
This now handles the containers correctly, but the compiler still considers this ambiguous for input_types that can be passed to the function. So we use a standard C++03 trick to make it prefer the first function over the second, of also passing a zero, and making the one we prefer accept and int, and the other takes ...
template<class input_type, class func_type>
auto rfor_each(input_type&& input, func_type&& func, int) ->decltype(func(input))
{ return func(input);}
//passing the zero causes it to look for a function that takes an int
//and only uses ... if it absolutely has to
template<class input_type, class func_type>
void rfor_each(input_type&& input, func_type&& func, ...)
{ for(auto&& i : input) rfor_each(i, func, 0);}
That's it. Six, relatively simple lines of code, and you can iterate over values, rows, or any other sub-unit, unlike all of the other answers.
#include <iostream>
int main()
{
std::cout << std::endl;
double B[3][3] = { { 1.2 } };
rfor_each(B[1], [](double&v){v = 5;}); //iterate over doubles
auto write = [](double (&i)[3]) //iterate over rows
{
std::cout << "{";
for(double d : i)
std::cout << d << ", ";
std::cout << "}\n";
};
rfor_each(B, write );
};
Proof of compilation and execution here and here
If you wanted a more convenient syntax in C++11, you could add a macro. (Following is untested)
template<class container>
struct container_unroller {
container& c;
container_unroller(container& c_) :c(c_) {}
template<class lambda>
void operator <=(lambda&& l) {rfor_each(c, l);}
};
#define FOR_NESTED(type, index, container) container_unroller(container) <= [](type& index)
//note that this can't handle functions, function pointers, raw arrays, or other complex bits
int main() {
double B[3][3] = { { 1.2 } };
FOR_NESTED(double, v, B) {
std::cout << v << ", ";
}
}
Firstly, you shouldn't use a vector of vectors of vectors. Each vector is guaranteed to have contiguous memory, but the "global" memory of a vector of vectors isn't (and probably won't be). You should use the standard library type array instead of C-style arrays as well.
using std::array;
array<array<array<double, 5>, 8>, 10> B;
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]);
// or, if you really don't like that, at least do this:
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]);
}
}
}
Better yet though, you could define a simple 3D matrix class:
#include <stdexcept>
#include <array>
using std::size_t;
template <size_t M, size_t N, size_t P>
class matrix3d {
static_assert(M > 0 && N > 0 && P > 0,
"Dimensions must be greater than 0.");
std::array<std::array<std::array<double, P>, N>, M> contents;
public:
double& at(size_t i, size_t j, size_t k)
{
if (i >= M || j >= N || k >= P)
throw out_of_range("Index out of range.");
return contents[i][j][k];
}
double& operator(size_t i, size_t j, size_t k)
{
return contents[i][j][k];
}
};
int main()
{
matrix3d<10, 8, 5> B;
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(i,j,k));
return 0;
}
You could go further and make it fully const-correct, add matrix multiplication (proper and element-wise), multiplication by vectors, etc. You could even generalise it to different types (I'd make it template if you mainly use doubles).
You could also add proxy objects so you can do B[i] or B[i][j]. They could return vectors (in the mathematical sense) and matrices full of double&, potentially?
One thing you may want to try if you only have statements in the inner-most loop - and your concern is more about the overly verbose nature of the code - is to use a different whitespace scheme. This will only work if you can state your for loops compactly enough so that they all fit on one line.
For your first example, I would rewrite it as:
vector< vector< vector<int> > > A;
int i,j,k;
for(k=0;k<A.size();k++) for(i=0;i<A[k].size();i++) for(j=0;j<A[k][i].size();j++) {
do_something_on_A(A[k][i][j]);
}
This is kinda pushing it because you are calling functions in the outer loops which is equivalent to putting statements in them. I have removed all unnecessary white-space and it may be passible.
The second example is much better:
double B[10][8][5];
int i,j,k;
for(k=0;k<10;k++) for(i=0;i<8;i++) for(j=0;j<5;j++) {
do_something_on_B(B[k][i][j]);
}
This may be different whitespace convention than you like to use, but it achieves a compact result that nonetheless does not require any knowledge beyond C/C++ (such as macro conventions) and does not require any trickery like macros.
If you really want a macro, you could then take this a step further with something like:
#define FOR3(a,b,c,d,e,f,g,h,i) for(a;b;c) for(d;e;f) for(g;h;i)
which would change the second example to:
double B[10][8][5];
int i,j,k;
FOR3(k=0,k<10,k++,i=0,i<8,i++,j=0,j<5,j++) {
do_something_on_B(B[k][i][j]);
}
and the first example fares better too:
vector< vector< vector<int> > > A;
int i,j,k;
FOR3(k=0,k<A.size(),k++,i=0,i<A[k].size(),i++,j=0,j<A[k][i].size(),j++) {
do_something_on_A(A[k][i][j]);
}
Hopefully you can tell fairly easily which statements go with which for statements. Also, beware the commas, now you can't use them in a single clause of any of the for
s.
One idea is to write an iterable pseudo-container class that "contains" the set of all multi-index tuples you'll index over. No implementation here because it'll take too long but the idea is that you should be able to write...
multi_index mi (10, 8, 5);
// The pseudo-container whose iterators give {0,0,0}, {0,0,1}, ...
for (auto i : mi)
{
// In here, use i[0], i[1] and i[2] to access the three index values.
}