C++中如何实现类似Eigen中的逗号分隔初始化方式?

21

以下是Eigen文档的一部分:

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

输出:

1 2 3
4 5 6
7 8 9

我无法理解如何通过运算符<<捕获所有逗号分隔的值。 我做了一个小实验:

cout << "Just commas: ";
cout << 1, 2, 3, 4, 5;
cout << endl;
cout << "Commas in parentheses: ";
cout << ( 1, 2, 3, 4, 5 );
cout << endl;
可预见地(根据我对C++语法的理解),operator<< 只捕获了其中一个值:
Just commas: 1
Commas in parentheses: 5

因此,问题的标题。


4
很可能他们已经重载了operator,()来允许这样做。 - NathanOliver
1
找到这个源代码。 - NathanOliver
3个回答

27
基本思想是重载<<,操作符。
重载m << 11放入m中,并返回一个特殊的代理对象,称为p,它持有对m的引用。
然后重载p, 22放入m中并返回p,因此p, 2, 3将首先将2放入m中,然后是3
类似的技术在Boost.Assign中也使用,但他们使用+=而不是<<

10

这是一个可能的简化实现。

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);
    }
};

这个想法是<<返回一个Loader实例,该实例等待第二个元素;每个加载器实例使用逗号运算符来更新矩阵并返回另一个加载器实例。

请注意,通常认为重载逗号运算符是一种不好的做法,因为该运算符最具体的特征是严格的从左到右的评估顺序。然而,当重载时,这不能保证,并且例如在以下情况下:

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

g()可能在f()之前被调用。

请注意,我所指的是求值顺序,而不是显然也适用于重载版本的结合性或优先级。 例如,g()可能在f()之前被调用,但从f()得到的结果保证正确地放置在矩阵中,而来自g()的结果则放置在之后。


当重载operator,时,为什么顺序不能保持不变?难道m << f(), g()不只是运算符的组合,遵循从左到右严格的评估规则吗? - vsoftco
1
@vsoftco:对于重载版本的“,”、“&&”和“||”,其求值顺序不能保证(这是唯一保证本机版本求值顺序的运算符)。您不应将求值顺序与结合性和优先级混淆(当然,这些都由重载版本维护)。换句话说,可能会在调用“f()”之前调用“g()”,但来自“f()”的值将在来自“g()”的值之前插入矩阵中。 - 6502
谢谢,你说得对,我意识到我实际上是在考虑结合性。 - vsoftco

3
逗号本身是C++中的运算符,可以被重载(似乎Eigen已经这样做了)。我不知道Eigen具体如何实现重载,但我确定您可以搜索Eigen源代码来查找相关信息。
对于您的小实验,您需要了解C++中未重载逗号运算符的工作方式。
逗号运算符的形式为<statement>,<statement>,并且计算结果为第二个语句的计算结果。运算符<<的优先级高于运算符,。因此,在评估其余逗号操作之前,会先评估cout
由于,是从左到右结合的,因此代码(1,2,3,4,5)等同于((((1,2),3),4),5),其计算结果为最右侧的值,即5

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接