There are many posts (questions) about comma operator overloading in c++. Most of the answers advice to DO NOT use comma operator overloading. I want to write a c++ library with a syntax very similar to Matlab language. The basic object is a Matrix MX. I want to be able to make the end-user of the library to write expressions like :
MX a = b(i);// get b elements at indices i
b(i,j)= a; // set elements of b at indices i,j.
I have an idea about how to do make the setter & the getter work as written above using a Proxy Class that save a pointer to the MX object and save also the indices i,j objects. For example b(i,j) will create a Proxy object ProxMX(b,i,j). Then we define a method to assign ProxMX to MX and visversa (using operator =) that do the hard job of getting & setting the elements of b.
I need help to make function calls like :
(x,y,z)= ff(a,b,c)
Where a, b, c are inputs argments (MX objects) and x, y , z are output argments . if the above syntaxe is not not possible i can think about a syntaxe like :
ff((a,b,c), (x,y,z) )
I started writing this test code:
#include "stdafx.h"
#include <iostream>
#include <fstream>
#include <vector>
using namespace std;
class MX {// Matrix
public:
MX(double va) {
elem=va;// only one double for the moment to test the syntaxe
}
MX &operator ()(MX idx){ // get & set MX(i)
return *this;//
};
MX &operator ()(MX idx1,MX idx2) { // get set MX(i,j)
return *this;
} ;
friend ostream &operator<<(ostream &stream, MX a);
double elem;
};
ostream &operator<<(ostream &stream, MX a)
{
stream << a.elem ;
return stream;
}
typedef vector<const MX > MXR;
class ArgList { // Proxy
public:
//ArgList(const MX& a){
// data.push_back(a);
//}
ArgList() {};
ArgList& operator , (const MX &a){
data.push_back(a);
return *this;
}
ArgList& operator =(ArgList& ins){
for (int i=0 ;i <ins.data.size();i++)
(this->data[i]).elem=ins.data[i].elem;
return *this;
};
MXR data;
};
ArgList operator , (const MX& a, const MX& b){
ArgList out;
out.data.push_back(a);
out.data.push_back(b);
return out;
}
ArgList ff(ArgList argins)
{
int n = argins.data.size();
MX a= argins.data[0];
MX b= argins.data[1];
MX x(a.elem+1.0);
MX y(b.elem+10.0);
MX z(a.elem+b.elem);
return ( x, y , z);
}
void gg(ArgList argins, ArgList &argout)
{
int n = argins.data.size();
MX a= argins.data[0];
MX b= argins.data[1];
MX x(a.elem+1.0);
MX y(b.elem+10.0);
MX z(a.elem+b.elem);
argout = ( x, y , z);
}
int _tmain(int argc, _TCHAR* argv[])
{
MX a(1.0);MX b(2.0);MX c(3.0);
MX aa = a(MX(3.0));
aa(MX(2.0),MX(3.0))=MX(5.0);
cout << "a=" << a << ",b=" << b << ",c=" << c << endl;
MX x(0.0);MX y(0.0);MX z(0.0);
cout << "x=" << x << ",y=" << y << ",z=" << z << endl;
(x,y,z)= ff((a , b, c ));
cout << "x=" << x << ",y=" << y << ",z=" << z << endl;
gg((a,b,c) , (x,y,z));
cout << "x=" << x << ",y=" << y << ",z=" << z << endl;
return 0;
}
This code compiles & runs without errors using VS2010 Express :). But as expected it does not give the expected results because I need to save references to variables in the ArgList instead of making a copy of objects to vector. I know that we cannot use std::vector as a container of references to objects.
Any help in order to make theses expressions writables and working in c++ code. Thanks.
I'm using C++11 to outline you a solution (the code is tested, too), but this is doable in C++03 (using e.g. Boost.Tuple):
// Base case
template<typename Lhs, typename Rhs>
std::tuple<Lhs&, Rhs&>
operator,(Lhs& lhs, Rhs& rhs)
{
return std::tie(lhs, rhs);
}
// General case when we already have a tuple
template<typename... Lhs, typename Rhs>
std::tuple<Lhs..., Rhs&>
operator,(std::tuple<Lhs...>&& lhs, Rhs& rhs)
{
return std::tuple_cat(lhs, std::tie(rhs));
}
Usage looks like (assuming this operator resides in namespace ns
):
// Declaration: note how ff must return a tuple
std::tuple<X, Y, Z> ff(A, B, C);
A a = /* ... */;
B b = /* ... */;
C c = /* ... */;
X x; Y y; Z z;
using ns::operator,;
// brackets on the left-hand side are required
(x, y, z) = ff(a, b, c);
Here are the attached caveats:
as you've seen, you need a using declaration to bring operator,
in scope. Even if the types X
, Y
, Z
reside in the same scope of operator,
(to enable ADL), std::tuple
doesn't. You could do something like template<typename... T> struct tuple: std::tuple<T...> { using std::tuple<T...>::tuple; };
(not as convenient to do in C++03 however) to have your own tuple in the appropriate namespace to have ADL in a quick-and-dirty way. However:
overloaded operators must always operate on at least one user-defined type. So if types X
and Y
both happen to be types like int
or double
, then you get the default operator,
. The usual solution to things like this is to require the client to do instead something like (ref(x), ref(y), z) = ff(a, b, c);
where ref
will return a type in the appropriate namespace (for ADL purposes, again). Perhaps such type can be implemented in terms of std::reference_wrapper
(or the Boost version, for C++03) with the same quick-and-dirty hack as for the tuple. (You'd need additional overloads of operator,
.)
All in all, that's a lot of work (with ugly workarounds) when something like
/* declaration of ff and variable definitions the same as before */
std::tie(x, y, z) = ff(a, b, c);
or perhaps
/* this skips unnecessary default constructions of x, y, z */
auto tuple = ff(a, b, c);
using std::get;
auto& x = get<0>(tuple);
auto& y = get<1>(tuple);
auto& z = get<2>(tuple);
works out of the box (even in C++03 with Boost.Tuple). My advice in this matter would be (with no slight intended): Keep it simple, stupid! and to use that.