在写一种很新的代码

大概十多年前的游戏之作,与君和之。
abused c++ : overloading operator<-
(今天不是4月1日,下面的程序也和Stroustrup博士关于运算符重载那篇著名论文里的
程序不同,应该可以在任何合理的C++编译器上编译。)
C++的=和==运算符属于“不数学”(unmathematical)的语言特性,当设计新的类型时,
你可能希望比基本类型显得更“数学”一点,也就是说:
用 a<-b 做赋值,用 a=b 表示相等。
我们先定义一个简单的类型做讨论的对象:
struct Int {
Int(int n=0) : value(n) {}
operator int() { return value; }
private:
int value;
};
相等判断很容易:
struct Int {
// ...
bool operator=(Int rhs) { return value == rhs.value; }
// ...
};
赋值就麻烦一点了,C++并没有<-这个运算符。
好在a<-b还是个合法的表达式,解析为a<(-b),也就是operator<(a, operator-(b))。
显然我们应该在operator-的结果中保存b的值,在operator<中完成赋值操作。
先定义operator-的结果类型:
template<typename T> struct assignment_tag {
const T& src;
explicit assignment_tag(const T& n) : src(n) {}
};
然后做运算符重载:
struct Int {
// ...
assignment_tag<Int> operator-() { return assignment_tag<Int>(*this); }
void operator<(const assignment_tag<Int>& rhs) { value = rhs.src.value; }
// ...
};
测试一下:
int main()
{
Int a(1), b(2);
cout<<"a="<<a<<" b="<<b<<'\n';
a<-b;
cout<<"a="<<a<<" b="<<b<<'\n';
if (a=b)
cout<<"a=b\n";
else
cout<<"a<>b\n";
}
$g++ overload.cpp -o overload
$overload
a=1 b=2
a=2 b=2
a=b
Bingo! 一共用了9行(算上空行)达到目的。如果你觉得这个办法不错的话,请注意
本文的标题。构造这个例子的目的,是要演示对C++运算符重载机制的滥用。
我们看看,在上面的程序中,b<-0会有什么效果:
首先operator-(int)不能被重载,-0一定得到0。于是b<-0相当于b<0,而我们没有定
义operator<(Int, int),于是根据函数重载规则,使用operator<(
b.operator int(), 0),作了一次无意义的比较。
如果可以重载operator-(int)就好了?完全以某一类型为参数的运算符,从概念上属
于该类型的interface。既然不能更改基本类型的定义,C++要求重载运算符的参数不
能全是基本类型是合理的。
问题在于,我定义的两个运算符的行为与基本类型相差太远。没人期望if (a < (-b))
不是对a和-b比较大小(虽然我有意令operator<返回void以禁止此写法)。这使得我
们的设计更加的“非数学”。
C++可以说是“成人”的语言,正如Stroustrup喜欢说的,"trust the programmer"。当
我明确表示我要做一些“危险”的事情时,编译器认为我知道自己在干什么并且准备为
其后果负责。总认为编译器比你自己更懂得不做什么对你“有好处”的语言,总给人一种
不舒服的感觉。
任何东西都可以被滥用,也可以被利用。C++中可做的事情很多,所以可以滥用的地方
也不少。那么上面这种滥用对应的利用是什么?Expression templates。由高优先级运
算符(operator-)返回与子表达式同构的信息(assignment_tag),低优先级运算符
(operator<)利用此信息完成整个表达式的运算,这就是expression templates基本实现
方法。
例如:
T a, b, c;
// ...
c = a+b;
你可以分别定义T operator+(T, T)和T::operator=(T),也可以用expression template
的方式做:
struct addition_tag {
const T& lhs
const T& rhs;
addition_tag(const T& lhs_, const T& rhs_)
: lhs(lhs_), rhs(rhs_) {}
};
addition_tag operator+(const T& lhs, const T& rhs)
{ return addition_tag(lhs, rhs); }
T& T::operator=(const addition_tag& src)
{
// do something with *this, src.lhs and src.rhs
}
为什么要这么麻烦?因为知道的信息越多,越有利于优化。普通的+和=只知道两个对
象,而expression templates版本的=知道表达式中的全部三个对象。expression
templates的初衷就是利用这多出来的信息消去表示中间结果的临时对象。
关于expression templates的详细介绍,见T. Veldhuizen, "Expression Templates"
http://osl.iu.edu/~tveldhui/papers/Expression-Templates/exprtmpl.html
Q&A
Q: 表示“相等”的operator=不该是普通函数吗(像operator==通常的做法)?
A: 应该。但是C++要求operator=是非const成员函数。这也是不应该把=这样常用的
运算符挪作他用的一个原因:它们有很多限制。
Q: assignment_tag似乎没什么用。令operator-返回T,并重载operator<(T, T)不行
吗?
A: 可以,但是我要附会到expression templates上去。;-)
Q: 重载b->a等价于a<-b怎么样?
A: 不行。->的右边必须是某struct的成员名字,除非使用宏,目前不可能在编译时
生成一个包含成员"some_random_name"的struct。
如果不嫌长,可以重载-->。同时重载assignment_tag::operator--(int)的话,
甚至可以写 a<-------b或者b------>a,限制是箭头向右时'-'必须是偶数个。
另外,数学家似乎只使用a<-b的形式。
Q: 为什么不能写a<-b<-c,像基本类型的operator=那样?
A: 我们的目标就是和基本类型不一样。:-)
写成(a, b)<-c可能更自然:
pair<Int&, Int&> operator,(Int& lhs, Int& rhs)
{ return pair<Int&, Int&>(lhs, rhs); }
void operator<(pair<Int&, Int&> lhs, assignment_tag rhs)
{
lhs.first<rhs;
lhs.second<rhs;
}
类似的手法,(a, b) = (b, a) 或者 a<b<c 都可以做到。但是,再欣赏python
也不该这样滥用C++。:-(