欢迎光临散文网 会员登陆 & 注册

在写一种很新的代码

2023-08-14 01:18 作者:genericity  | 我要投稿

大概十多年前的游戏之作,与君和之。


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++。:-(


在写一种很新的代码的评论 (共 条)

分享到微博请遵守国家法律