用 Boost.Spirit 搭 std::string 使用時容易遇到的問題

這個問題太多人問了,
所以還是先寫下來當成 FAQ 用;
官方文件的 quick start 這一章是類似下面這樣教:

加上 The Scanner and Parsing 那一小節也提到 pharse-level parsing 有兩種版本:

導致有人寫出這種 code 來結果編不過在那邊哭:

其實這個也沒有什麼好廢話的。
官方文件從來都沒有這樣示範過,
因為 r 跟 +hex_p 其實是不同的 type,
直接把 +hex_p 丟進 parse() 跟把 r 丟進 parse() 是完全不同的意思。
所以不要亂寫。

光是用 rule<> 的預設參數是不行的,
因為它的預設引數是長這樣:

然後 scanner<> 的預設引數是長這樣:

很顯然 IteratorT 這欄預設是 char const*,
而送進 parse() 前兩個引數的值分別為 str.begin() 和 str.end();
想當然爾兩個 type 不合當然是會無法通過編譯。
錯誤訊息大致如下:

一般來說如果一個未開發或開發中國家的國民遇到這種錯誤訊息,
可能幾十萬個裡面才會有一個人認真去看它是在做什麼;
不過倒是十個裡面會有七八個看都不看直接改成這樣:

但是非常遺憾。
這樣雖然會通過編譯,
但是結果會是錯的。
如果 check 一下 parse(str.c_str(), r, space_p).full 的值就會知道它是 false,
debug 一下就會知道 parse 完 "ffff" 以後就因為遇到空格而直接 fail 了。
期望的明明是 phrase-level parsing,
結果用這種爛方法亂 try 變成了 character-level parsing 的效果。
因為真的看過太多一心想著便宜行事就好的懶惰鬼,
所以當他們改出這種 code 跑來問為什麼不會正常 work 的時候,
我都是不會給他們好臉色看;
因為我實在是太清楚他們是用怎樣的心態改出那一行來了。

前面也說了問題的癥結出在 r 而不是 iterator 和 C-style string 的問題。
有些人就算這樣提示了還是改成上面那個樣子,
這種人要不就是腦殘要不就是根本就是欠人家罵;
不過現在也罵到懶了乾脆在這裡連未來的份也一次先罵完。
總之有認真讀過 In-depth: The Scannner 那一章的大概也知道答案了。
就是要自訂一個 scanner type 出來:

然後用 rule<string_phrase_scanner_t> r; 來定義 rule 就沒問題了;
不過因為 IteratorT 填的是 string::const_iterator,
所以照原本的 code 那樣寫也還是會出現 error,
但是正常人應該會把上面那種 code 整理成一個 function:

所以它先天上就一直會是 string::const_iterator,
至於腦袋只想著把它改成 string::iterator 的就請自己去跳樓吧。
雖然說也不是沒有辦法讓它變聰明點能同時吃 const 跟 non-const,
不過沒有必要這樣做。

另外,
比較大型的 parser 其實都會把 rules 整理成 grammar。
用了 grammar 以後就不會遇到這種問題了,
只是跟那些沒救的人說要寫成 grammar 他們大概也懶了吧,
因為要多打不少字。


2009-11-22 更新:

事實上現在大部分的 compiler 已經支援 C++0x。
這個問題的主要癥結在於精確的指定 r 的 type 是既麻煩又困難的(不懂的話可以想想如果它是由一堆 | 和 >> 合成出來的 rule 時該是什麼 type),
所以 spirit 才會設計 rule<> 這種 base class 去接收它,
而這個缺點就是執行期多了一個 virtual function call 導致最佳化困難等效能上的困擾。
現在我們有了 automatic type deduction 這個高科技兵器,
問題也能迎刃而解了: