在寫一種很新的代碼

大概十多年前的游戲之作,與君和之。
abused c++ : overloading operator<-
(今天不是4月1日,下面的程序也和Stroustrup博士關(guān)于運算符重載那篇著名論文里的
程序不同,應(yīng)該可以在任何合理的C++編譯器上編譯。)
C++的=和==運算符屬于“不數(shù)學(xué)”(unmathematical)的語言特性,當(dāng)設(shè)計新的類型時,
你可能希望比基本類型顯得更“數(shù)學(xué)”一點,也就是說:
用 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還是個合法的表達(dá)式,解析為a<(-b),也就是operator<(a, operator-(b))。
顯然我們應(yīng)該在operator-的結(jié)果中保存b的值,在operator<中完成賦值操作。
先定義operator-的結(jié)果類型:
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行(算上空行)達(dá)到目的。如果你覺得這個辦法不錯的話,請注意
本文的標(biāo)題。構(gòu)造這個例子的目的,是要演示對C++運算符重載機(jī)制的濫用。
我們看看,在上面的程序中,b<-0會有什么效果:
首先operator-(int)不能被重載,-0一定得到0。于是b<-0相當(dāng)于b<0,而我們沒有定
義operator<(Int, int),于是根據(jù)函數(shù)重載規(guī)則,使用operator<(
b.operator int(), 0),作了一次無意義的比較。
如果可以重載operator-(int)就好了?完全以某一類型為參數(shù)的運算符,從概念上屬
于該類型的interface。既然不能更改基本類型的定義,C++要求重載運算符的參數(shù)不
能全是基本類型是合理的。
問題在于,我定義的兩個運算符的行為與基本類型相差太遠(yuǎn)。沒人期望if (a < (-b))
不是對a和-b比較大?。m然我有意令operator<返回void以禁止此寫法)。這使得我
們的設(shè)計更加的“非數(shù)學(xué)”。
C++可以說是“成人”的語言,正如Stroustrup喜歡說的,"trust the programmer"。當(dāng)
我明確表示我要做一些“危險”的事情時,編譯器認(rèn)為我知道自己在干什么并且準(zhǔn)備為
其后果負(fù)責(zé)??傉J(rèn)為編譯器比你自己更懂得不做什么對你“有好處”的語言,總給人一種
不舒服的感覺。
任何東西都可以被濫用,也可以被利用。C++中可做的事情很多,所以可以濫用的地方
也不少。那么上面這種濫用對應(yīng)的利用是什么?Expression templates。由高優(yōu)先級運
算符(operator-)返回與子表達(dá)式同構(gòu)的信息(assignment_tag),低優(yōu)先級運算符
(operator<)利用此信息完成整個表達(dá)式的運算,這就是expression templates基本實現(xiàn)
方法。
例如:
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
}
為什么要這么麻煩?因為知道的信息越多,越有利于優(yōu)化。普通的+和=只知道兩個對
象,而expression templates版本的=知道表達(dá)式中的全部三個對象。expression
templates的初衷就是利用這多出來的信息消去表示中間結(jié)果的臨時對象。
關(guān)于expression templates的詳細(xì)介紹,見T. Veldhuizen, "Expression Templates"
http://osl.iu.edu/~tveldhui/papers/Expression-Templates/exprtmpl.html
Q&A
Q: 表示“相等”的operator=不該是普通函數(shù)嗎(像operator==通常的做法)?
A: 應(yīng)該。但是C++要求operator=是非const成員函數(shù)。這也是不應(yīng)該把=這樣常用的
運算符挪作他用的一個原因:它們有很多限制。
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,限制是箭頭向右時'-'必須是偶數(shù)個。
另外,數(shù)學(xué)家似乎只使用a<-b的形式。
Q: 為什么不能寫a<-b<-c,像基本類型的operator=那樣?
A: 我們的目標(biāo)就是和基本類型不一樣。:-)
寫成(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++。:-(