How could comma separated initialization such as in Eigen be possibly implemented in C++?
Asked Answered
F

3

21

Here's a part of Eigen documentation:

Matrix3f m;
m << 1, 2, 3,
     4, 5, 6,
     7, 8, 9;
std::cout << m;

Output:

1 2 3
4 5 6
7 8 9

I couldn't understand how could all the comma separated values be captured by operator<< above. I did a tiny experiment:

cout << "Just commas: ";
cout << 1, 2, 3, 4, 5;
cout << endl;
cout << "Commas in parentheses: ";
cout << ( 1, 2, 3, 4, 5 );
cout << endl;

Predictably (according to my understanding of C++ syntax) only one of the values was captured by operator<< :

Just commas: 1
Commas in parentheses: 5

Thus the title question.

Figuration answered 8/4, 2015 at 18:58 Comment(2)
More than likely they have overloaded the operator,() to allow this.Bahia
Found this source code.Bahia
V
27

The basic idea is to overload both the << and the , operators.

m << 1 is overloaded to put 1 into m and then returns a special proxy object – call it p – holding a reference to m.

Then p, 2 is overloaded to put 2 into m and return p, so that p, 2, 3 will first put 2 into m and then 3.

A similar technique is used with Boost.Assign, though they use += rather than <<.

Vitalis answered 8/4, 2015 at 19:14 Comment(0)
H
10

This is a possible simplified implementation

struct M3f {
    double m[3][3];
    struct Loader {
        M3f& m;
        int i;
        Loader(M3f& m, int i) : m(m), i(i) {}
        Loader operator , (double x) {
            m.m[i/3][i%3] = x;
            return Loader(m, i+1);
        }
    };
    Loader operator<<(double x) {
        m[0][0] = x;
        return Loader(*this, 1);
    }
};

The idea is that << returns a Loader instance that waits for second element, and each loader instance uses the comma operator to update the matrix and returns another loader instance.

Note that overloading the comma operator is generally considered a bad idea because the most specific characteristic of the operator is strict left-to-right evaluation order. However when overloaded this is not guaranteed and for example in

m << f(), g(), ...

g() could end up being called before f().

Note that I'm talking about the evaluation order, not about associativity or precedence that are of course maintained also for overloaded versions. For example g() could be called before f() but the result from f() is guaranteed to be correctly placed in the matrix before the result from g().

Hertel answered 8/4, 2015 at 19:20 Comment(3)
Why isn't the order maintained when overloading operator,? Isn't m << f(), g() just a composition of operators, with strict rules of left-to-right evaluation?Triley
@vsoftco: the order of evaluation is not guaranteed for overloaded version of ,, of && and of || (the only operators where the evaluation order is guaranteed for the native version). You should not confuse the evaluation order with associativity and precedence (that are of course maintained by overloaded versions). In other words may be g() will be called before f() but the value from f() will be inserted in the matrix before the value from g().Hertel
Thanks, you're right, I realized that I was actually thinking about associativity.Triley
G
3

The comma itself is an operator in c++ which can be overloaded (and apparently is by eigen). I don't know the exact way eigen implements the overloading but I am sure that you can search for the sources of eigen to look it up.

To your little experient you have to understand how the unoverloaded comma operator works in c++.

The comma operator has the form <statement>,<statement> and is evaluated to whatever the second statement is evaluated to. The operator << has a higher precedence than the operator ,. Because of that the cout is evaluated before the rest of the comma operations are evaluated.

Because , is Left-to-right associative the code (1,2,3,4,5) is equal to ((((1,2),3),4),5) which evaluates to the most right value, which is 5.

Generality answered 8/4, 2015 at 19:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.