Reputation: 206
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.
Upvotes: 0
Views: 1719
Reputation: 206
Thank you all for your sugesstions & feedback. here is a working example using 2 proxies. ArgList contains pointers to MX objects and ArgCopyList contains copies of MX objects.
From a matlab user point of view the c++ syntaxe (x,y,...)=myfunc((a,b,...))
is very similar to the matlab expression [x,y,...]=myfunc(a,b,...)
. But my first impression is that this kind of function calling is not efficient at all because the returned values are copied which could be avoided by passing outputs by reference.
#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 = (const MX &src)
{ elem = src.elem;
return *this;
}
friend ostream &operator<<(ostream &stream, MX &a);
double elem;
};
ostream &operator<<(ostream &stream, MX &a)
{
stream << a.elem ;
return stream;
}
typedef vector<MX *> MXR; // save pointers only
class ArgCopyList { // copy the objects to a list
public :
vector<const MX> data;
ArgCopyList(MX &a)
{
data.push_back(a);
};
ArgCopyList(MX &a, MX &b)
{
data.push_back(a);
data.push_back(b);
};
ArgCopyList(MX &a, MX &b, MX &c)
{
data.push_back(a);
data.push_back(b);
data.push_back(c);
};
// do the same for bigger lists
};
class ArgList { // Proxy
public:
ArgList() {};
ArgList(const ArgList& src)
{
data.clear();
for (int i=0 ;i <src.data.size();i++)
data.push_back(src.data[i]);
}
ArgList& operator , ( MX &a){
data.push_back(&a);
return *this;
}
ArgList &operator= ( ArgList &src)
{
if (this == &src)
return *this;
data.clear();
int n= src.data.size();
for (int i=0 ;i <n;i++)
data.push_back(src.data[i]);
return *this;
};
ArgList& operator =( ArgCopyList& src){
for (int i=0 ;i <data.size();i++)// TBD : must control the size of src & this->data here
data.at(i)->elem = (src.data[i].elem);
return *this;
};
MXR data;
};
ArgList operator , (MX& a, MX& b){
ArgList out;
out.data.push_back(&a);
out.data.push_back(&b);
return out;
}
// test function
ArgCopyList 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 ArgCopyList( x, y , z);
}
int _tmain(int argc, _TCHAR* argv[])
{
MX a(1.0);MX b(2.0);MX c(3.0);
cout << "a=" << a << ",b=" << b << ",c=" << c << endl;
MX x(0.0);MX y(0.0);MX z(0.0);
cout << "Argouts before calling (x,y,z)= ff((a,b,c)).\nx=" << x << ",y=" << y << ",z=" << z << endl;
(x,y,z)= ff((a , b, c) );
cout << "Argouts after calling ff.\nx=" << x << ",y=" << y << ",z=" << z << endl;
return 0;
}
/* output
a=1,b=2,c=3
Argouts before calling (x,y,z)= ff((a,b,c)).
x=0,y=0,z=0
Argouts after calling ff.
x=2,y=12,z=3
*/
Upvotes: 0
Reputation: 254471
In C++11, you can do this using tuples:
std::tuple<some_type, another_type, yet_another_type> ff(...);
some_type x;
another_type y;
yet_another_type z;
std::tie(x,y,z) = ff(a,b,c);
If your compiler doesn't support that, the Boost.Tuple library is very similar, but may be more restricted without support from variadic templates.
If you really want to support syntax like (x,y,z) = ff(a,b,c);
, which might be rather confusing to a C++ programmer even if it does look sensible in Matlab, then you're almost there. You need a separate type similar to your ArgList
(perhaps called something like RefList
), which contains pointers rather than values, initialises these pointers from non-const
references to the results, and has an assignment operator that takes an ArgList
(or some other collection of values) and assigns each element via the pointers.
You might also want to look at Boost.Assignment to see how that kind of operator overloading can be made to work. It's a bit of a hassle; in particular, you can't overload operators that act only on built-in types, which rather limits the usefulness of that approach. Personally, I'd use variadic templates instead if C++11 is an option.
Upvotes: 2
Reputation: 35449
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.
Upvotes: 2