說說 C++ 的 Concept
其實吧,在 C++ 11 還是 0x 的年代,Concept 有 concept map,就是說唉你這個類型沒這個操作,我給你 map 到其它的操作,這樣它也滿足這個 concept;然後還有 axiom,就是說對語義上的約束,例如你寫個 axiom 表示某個操作滿足可交換律,然後編譯器就可以利用這個 axiom 進行優化;然後就是 constraint,就是說限制你這個類型特定的操作要是合法的。
結果後來發現這太複雜了,編譯器實現好像也不是太那啥,然後 14 年的時候弄了個 Concept Lite,就是把前兩部分給幹掉了,只剩 constraint 了(然後 BS 又打算讓 axiom 那一部分在 Contracts 那塊復活了)。
唉這個 constraint 啊,就是約束你這個類型哪些操作合法,像這樣:
template<typename T>nconcept bool Tickable = requires (T t) { t.tick(); };n
這段就是說,啊,現在來一個 T 類型的對象 t,唉只要它 t.tick() 這個表達式合法就行:
struct Foon{n void tick() {}n void tock() {}n};nnvoid Bar(Tickable& t)n{n t.tick();n}n
唉終於不用什麼奇怪的 SFINAE 什麼奇怪的 std::declval<T>() 了,哎呀太好了。
其實這個東西吧,就是一個靜態介面的概念,要是在 Java 裡面你想表達啊,來個類,它必須有 tick 方法,你就得弄個 interface Tickable 什麼的,姑且把它稱作「動態介面」。所謂的面向介面編程,好處之一就是代碼重用嘛,就像你在 Java 弄了 Tickable 這個 interface,那麼你弄個手錶類也行,弄個原子鐘類也沒人管你,代碼也不用改。靜態介面也是,萬一有一天你發現現在這個 tick 類不行了,換成其他的一樣有 tick 函數的類,你的代碼照樣可以編譯。
這時候 C++ Concept 的缺陷就來了——你在 Bar 函數里還可以使用 t 上的除了 tick 函數的其它函數:
void Bar(Tickable& t)n{n t.tock(); // WTF??n t.tick();n}n
這個時候呢,你傳 Foo 的對象進去是可以編譯過的,但是傳一個沒有 tock() 的類的對象呢?!炸了。也就是說,C++ 的靜態介面不是那麼「介面」。
那麼看向其它語言——C# 吧就說,它靜態介面就很嚴格——你 where 裡面沒寫明白的都不讓你用。這種必須都寫明白才能用的方式在 C++ 社區里稱作 full template definition checking。C++ 的 Concept 不可能做到這樣,因為:
- 兼容性
你這麼搞了,之前的代碼怎麼辦?之前沒有 concept 的代碼都是無約束的,這麼改直接全死。
2. 編譯性能
我的天,你想想,C++ ,每個函數裡面每一個表達式都檢查一通,嘖嘖嘖……
但是你不這麼搞,那這個 Concept 的作用就沒啥意思了——萬一不小心用了什麼沒約束的操作錯誤信息不還是很難看。
要我說,這方面 Rust 做得太好了。我認為 Rust 的這個 Trait 真是把靜態介面和所謂的「動態介面」完美地、無縫地結合到一塊去了。真好。
Concept 的問題遠不止這些,但是從我的角度看,這個最可怕。
The C++0x "Remove Concepts" Decision
Why Concepts didn』t make C++17
推薦閱讀:
※[譯] C++中帶狀態元編程黑科技(二):實現常量表達式計數器
※C++模板元編程--replace_type<>
※C++模板元編程---編譯期類成員檢測
※萌新刷題(一)A + B 問題
※刷題大戰 09 代碼評審報告
TAG:C |
