Search code examples
c++referenceoperator-overloadingcomma-operator

C++ comma operator overloading and vector of references


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.


Solution

  • 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.