Overload comma , operator to init the array

Started by
16 comments, last by Misery 11 years, 8 months ago
Hello,

I would like to overload comma , operator so it would be able to init the array.
The array is in the class:

template <class DataType,class IntType>
class Matrix
{
public:
DataType *Elements;
IntType Rows, Cols;
//some other functions and operators
};


How do I achieve the functionality lik in the blitz++ library, that allows to use such syntax:

Matrix<float,int> M;
M.Resize(2,2);
M=1, 0,
0, 1;


I have read some tutorials on internet and posts on gamedev and stackoverflow but I havent found what I was really looking for. Also tried to find implementation in blitz++ of this feature but without success.

Any help or suggestions would be appreciated,
Thanks in advance
Advertisement
Overloading the comma operator for that usage is considered evil. Anytime you overload an operator for a usage that departs from what is normal, you create potential confusion for users using your code. Adding to the evilness, commas are used multiple places in C++ for multiple different purposes (unlike other operators)... but only one usage of the comma overloadable, and has lower priority than the others, which may lead to more confusion from time to time.

A much better initialization would be the more common, and thus more socially (among programmers) acceptable:
m = {1, 0, 0, 1};

However, if you want to go ahead and do it anyway, here's how:
MyClass &MyClass::operator ,(int value)
{
//...do whatever with 'value'.

return *this; //Return a reference to myself.
}


You overload the operator, handle the (one!) parameter, then you return a reference to yourself for the next operator to work on.
myClass , a, b, c, d

Is parsed as:
((((myClass , a) , b) , c) , d)

...as the return value is a reference to yourself to call the next operator.

If you want to use the more preferable way:
//Initialize:
MyClass m = {1, 0, 0, 1};

//Or assign later:
m = {0, 2, 11, 17};


...you can do so through the new C++11's standard std::initializer_list<>, if you are using an up-to-date compiler with C++11 enabled. This is what the standard library does now.
Please, do not overload operators that people don't expect to be overloaded. Overloading operators like ",", "&&" and "||" is particularly evil because their evaluation properties (look up sequence points and short-circuit evaluation) change whether they are overwritten or not, so the potential for obfuscation is enormous.
Who cares if he overloads the , operator anyway? Its not like he is going to be doing anything useful for the next few years, given his choice of language.

As an empiricist, I say let him do it and learn the consequences for himself.
Don't thank me, thank the moon's gravitation pull! Post in My Journal and help me to not procrastinate!
Actually the initializer list solves the whole problem very well. But just one more question.
How do I make a constructor that could build the matrix using syntax like that (assuming that one can write here any number of rows):

M={
{1,2,3} //row 1
{4,5,6} //row 2
{7,8,9} //row 3
};

That is a problem because matrix defined in such manner can have any number of arguments (rows). How do I make that working? Should I use a variadic template?

Actually the initializer list solves the whole problem very well. But just one more question.
How do I make a constructor that could build the matrix using syntax like that (assuming that one can write here any number of rows):

M={
{1,2,3} //row 1
{4,5,6} //row 2
{7,8,9} //row 3
};

That is a problem because matrix defined in such manner can have any number of arguments (rows). How do I make that working? Should I use a variadic template?


Unfortunately, this is not as clean:
https://groups.googl...1d7802958427622

However, you may consider supplying the arguments similarly to what you had in mind in your [font=courier new,courier,monospace]operator,[/font] solution:
M={
1,2,3, //row 1
4,5,6, //row 2
7,8,9 //row 3
};
@Matt-D: That of course is a solution (and a good one really). But absolutely beautiful it would be if I could use the syntax with lists in the list.
Actually I have created a template variadic constructor, and it works. However there is only one more thing: how can I find out the number of arguments (rows) given so I could allocate the *Data?

template <class ... Args>
Matrix(Args ... args)
{
//do stuff
}


When I use a code like the one given with "rows", it calls this constructor. If I could somehow estimate the number of rows, I wouldn't need to call Alloc before using the lists.
You could also create a temporary and hidden struct just for initialization, if you really want that syntax.

Something like this:

template <typename Type>
class MyMatrixRow
{
public:
MyMatrixRow(Type column1, Type column2, Type column3) :
column1(column1), column2(column2), column3(column3) { }

Type column1, column2, column3;
};

template <typename Type>
class MyMatrix
{
public:
MyMatrixRow(MyMatrixRow<Type> row1, MyMatrixRow<Type> row2, MyMatrixRow<Type> row3)
{
data.resize(9);

data[0] = row1.column1;
data[1] = row1.column2;
data[2] = row1.column3;

data[3] = row2.column1;
data[4] = row2.column2;
data[5] = row2.column3;

data[6] = row3.column1;
data[7] = row3.column2;
data[8] = row3.column3;
}

private:
std::vector<Type> data;
};


The structs passed in to the constructor are just used for constructing and then discarded.

The code:
MyMatrix<float> matrix = {{0.0f, 0.0f, 0.0f},
{0.0f, 0.0f, 0.0f},
{0.0f, 0.0f, 0.0f}};


Actually turns into:
MyMatrix<float> matrix(MyMatrixRow<float>(0.0f, 0.0f, 0.0f),
MyMatrixRow<float>(0.0f, 0.0f, 0.0f),
MyMatrixRow<float>(0.0f, 0.0f, 0.0f));


...and hopefully the entire MyMatrixRow() thing would be compiled out, but I don't know anything about compiler optimizing so I can't say for sure.
OMG why doesn't initializer_list constructors work in VS2010?? sad.png
On Linux GCC it works just fine :|

EDIT: it doesn't support this functionality so I have to cancel it at the moment, as my project has to be portable. Pity.

Anyway thanks for help very much.
Thanks to (Servant of the Lord)'s idea I've managed to do a syntax that works exactly as I wanted, and - at least to me (but I'm not an experienced programmer) - seems quite effective. To get even nicer, this solution cannot be used in VC++ but, it doesn't collide with it (maybe someday they will support init lists, as they have appropriate headers and stuff). So this initializer constructor can be defined, but cannot be used in VC++, but works with compiers that support this feature. I have tested it in Ubuntu Linux 12.04 with its default GCC compiler suite. I'm just putting it here if someone would search for such snippet.

Thanks Gyus for Your time! And special thanks for suggestions not to overload comma operator. :]

The row class:

template<class DATA_TYPE,class INT_TYPE>
class Row
{
public:
Row()
{ }
Row(const initializer_list<DATA_TYPE> &list) : L(list)
{ }
DATA_TYPE operator [](INT_TYPE i) const
{
return *(L.begin()+i);
}
INT_TYPE Size() const
{
return L.size();
}
private:
const initializer_list<DATA_TYPE> &L;
};


Matrix class:

template<class DATA_TYPE,class INT_TYPE>
class Matrix
{
public:
DATA_TYPE *Data;
INT_TYPE Rows,Cols;
Matrix()
{
cout<<"basic ctr"<<endl;
Data=NULL;
Rows=0;
Cols=0;
}
Matrix(initializer_list<DATA_TYPE> RowVec) //initializer for M={1,2,3,...,N}
{
Data=NULL;
Rows=0;
Cols=0;
Cols=RowVec.size();
if (Cols==0) Free();
else
{
Alloc(1,Cols);
INT_TYPE k=0;
typename initializer_list<DATA_TYPE >::iterator i=RowVec.begin();
for (i=RowVec.begin();i!=RowVec.end();i++) Data[k++]=*i;
}
}
Matrix(initializer_list<Row<DATA_TYPE,INT_TYPE> > RowsL) //initializer for M= { {}, {}, {}}
{
Data=NULL;
Rows=0;
Cols=0;
Rows=RowsL.size();
if (Rows==0) Free();
else
{
INT_TYPE k=0;
typename initializer_list<Row<DATA_TYPE,INT_TYPE> >::iterator i=RowsL.begin();
Cols=(*i).Size();
Alloc(Rows,Cols);
for (i=RowsL.begin();i!=RowsL.end();i++)
{
if (Cols!=(*i).Size()) { Free(); /* throw an error: inconsistient row/colums dimensions*/cout<<"Err: ircd"<<Cols<<endl;}
else
{
for (int j=0;j<(*i).Size();j++)
{
Data[j*Rows+k]=(*i)[j];
}
}
k++;
}
}
}
void Alloc(INT_TYPE R,INT_TYPE C)
{
Free();
Data=new DATA_TYPE[R*C];
Rows=R;
Cols=C;
}
void Free()
{
delete[] Data;
Data=NULL;
Rows=0;
Cols=0;
}
};


And now it is possible to use initialzier lists, in similar way as it is in Mathematica. So it is possible to init any rectangular array in a nice looking and intuitive fashion, for example:

Matrix<float,int> M,E,r,c;
M={
{0,1,0},
{0,0,0},
};

E={
{1,0,0},
{0,1,0},
{0,0,1}
};

r={3,2,1};
c={
{1},
{2},
{3},
{4}
}


Regards smile.png

PS.: #include <initializer_list>

This topic is closed to new replies.

Advertisement