如何在 C++ 中实现以逗号分隔的初始化,例如 Eigen 中的初始化?
How could comma separated initialization such as in Eigen be possibly implemented in C++?
这是 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
我不明白上面的 operator<< 怎么能捕获所有逗号分隔的值。我做了一个小实验:
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
故题题。
逗号本身是 c++ 中的一个运算符,它可以被重载(并且显然是通过 eigen 重载的)。我不知道 eigen 实现重载的确切方式,但我相信你可以搜索 eigen 的来源来查找它。
对于你的小经验,你必须了解 un 重载逗号运算符在 c++ 中的工作方式。
逗号运算符的形式为 <statement>,<statement>
并且计算结果为 second 语句计算结果。运算符 <<
的优先级高于运算符 ,
。因此 cout
在评估其余逗号操作之前先评估。
因为 ,
是从左到右关联的,所以代码 (1,2,3,4,5)
等于 ((((1,2),3),4),5)
,它的计算结果是最右边的值,即 5
。
基本思想是重载 <<
和 ,
运算符。
m << 1
被重载以将 1
放入 m
,然后 return 是一个特殊的代理对象——称之为 p
——持有对 m
.
然后p, 2
重载将2
放入m
和returnp
,这样p, 2, 3
会先把2
变成 m
然后 3
.
Boost.Assign 使用了类似的技术,尽管他们使用 +=
而不是 <<
。
这是一个可能的简化实现
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);
}
};
想法是 <<
returns 一个等待第二个元素的 Loader
实例,每个加载器实例使用逗号运算符更新矩阵和 returns另一个加载程序实例。
请注意,重载逗号运算符通常被认为是一个坏主意,因为该运算符最具体的特征是严格的从左到右的评估顺序。然而,当过载时,这并不能保证,例如在
m << f(), g(), ...
g()
可能会在 f()
之前被调用。
请注意,我说的是求值顺序,而不是关联性或优先级,它们当然也适用于重载版本。
例如 g()
可以在 f()
之前调用,但是 f()
的结果保证在 g()
.
的结果之前正确放置在矩阵中
这是 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
我不明白上面的 operator<< 怎么能捕获所有逗号分隔的值。我做了一个小实验:
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
故题题。
逗号本身是 c++ 中的一个运算符,它可以被重载(并且显然是通过 eigen 重载的)。我不知道 eigen 实现重载的确切方式,但我相信你可以搜索 eigen 的来源来查找它。
对于你的小经验,你必须了解 un 重载逗号运算符在 c++ 中的工作方式。
逗号运算符的形式为 <statement>,<statement>
并且计算结果为 second 语句计算结果。运算符 <<
的优先级高于运算符 ,
。因此 cout
在评估其余逗号操作之前先评估。
因为 ,
是从左到右关联的,所以代码 (1,2,3,4,5)
等于 ((((1,2),3),4),5)
,它的计算结果是最右边的值,即 5
。
基本思想是重载 <<
和 ,
运算符。
m << 1
被重载以将 1
放入 m
,然后 return 是一个特殊的代理对象——称之为 p
——持有对 m
.
然后p, 2
重载将2
放入m
和returnp
,这样p, 2, 3
会先把2
变成 m
然后 3
.
Boost.Assign 使用了类似的技术,尽管他们使用 +=
而不是 <<
。
这是一个可能的简化实现
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);
}
};
想法是 <<
returns 一个等待第二个元素的 Loader
实例,每个加载器实例使用逗号运算符更新矩阵和 returns另一个加载程序实例。
请注意,重载逗号运算符通常被认为是一个坏主意,因为该运算符最具体的特征是严格的从左到右的评估顺序。然而,当过载时,这并不能保证,例如在
m << f(), g(), ...
g()
可能会在 f()
之前被调用。
请注意,我说的是求值顺序,而不是关联性或优先级,它们当然也适用于重载版本。
例如 g()
可以在 f()
之前调用,但是 f()
的结果保证在 g()
.