這個問題太多人問了,
所以還是先寫下來當成 FAQ 用;
官方文件的 quick start 這一章是類似下面這樣教:
1 |
parse("ffff 1234 abcde", +hex_p, space_p); |
加上 The Scanner and Parsing 那一小節也提到 pharse-level parsing 有兩種版本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
template <typename IteratorT, typename ParserT, typename SkipT> parse_info<IteratorT> parse ( IteratorT const& first, IteratorT const& last, parser<ParserT> const& p, parser<SkipT> const& skip ); template <typename CharT, typename ParserT, typename SkipT> parse_info<CharT const*> parse ( CharT const* str, parser<ParserT> const& p, parser<SkipT> const& skip ); |
導致有人寫出這種 code 來結果編不過在那邊哭:
1 2 3 4 |
rule<> r = +hex_p; string str("ffff 1234 abcde"); parse(str.begin(), str.end(), r, space_p); |
其實這個也沒有什麼好廢話的。
官方文件從來都沒有這樣示範過,
因為 r 跟 +hex_p 其實是不同的 type,
直接把 +hex_p 丟進 parse() 跟把 r 丟進 parse() 是完全不同的意思。
所以不要亂寫。
光是用 rule<> 的預設參數是不行的,
因為它的預設引數是長這樣:
1 2 3 4 5 |
template< typename ScannerT = scanner<>, typename ContextT = parser_context<>, typename TagT = parser_address_tag> class rule; |
然後 scanner<> 的預設引數是長這樣:
1 2 3 4 |
template < typename IteratorT = char const*, typename PoliciesT = scanner_policies<> > class scanner; |
很顯然 IteratorT 這欄預設是 char const*,
而送進 parse() 前兩個引數的值分別為 str.begin() 和 str.end();
想當然爾兩個 type 不合當然是會無法通過編譯。
錯誤訊息大致如下:
1 2 3 4 5 6 7 |
/usr/include/boost/spirit/home/classic/core/non_terminal/impl/rule.ipp: In member function 'typename boost::spirit::classic::parser_result<DerivedT, ScannerT>::type boost::spirit::classic::impl::rule_base<DerivedT, EmbedT, T0, T1, T2>::parse_main(const ScannerT&) const [with ScannerT = boost::spirit::classic::scanner<__gnu_cxx::__normal_iterator<const char*, std::basic_string<char, std::char_traits<char>, std::allocator<char> > >, boost::spirit::classic::scanner_policies<boost::spirit::classic::skipper_iteration_policy<boost::spirit::classic::iteration_policy>, boost::spirit::classic::match_policy, boost::spirit::classic::action_policy> >, DerivedT = boost::spirit::classic::rule<boost::spirit::classic::nil_t, boost::spirit::classic::nil_t, boost::spirit::classic::nil_t>, EmbedT = const boost::spirit::classic::rule<boost::spirit::classic::nil_t, boost::spirit::classic::nil_t, boost::spirit::classic::nil_t>&, T0 = boost::spirit::classic::nil_t, T1 = boost::spirit::classic::nil_t, T2 = boost::spirit::classic::nil_t]': /usr/include/boost/spirit/home/classic/core/non_terminal/impl/rule.ipp:173: instantiated from 'typename boost::spirit::classic::parser_result<DerivedT, ScannerT>::type boost::spirit::classic::impl::rule_base<DerivedT, EmbedT, T0, T1, T2>::parse(const ScannerT&) const [with ScannerT = boost::spirit::classic::impl::phrase_parser<boost::spirit::classic::space_parser>::parse(const IteratorT&, const IteratorT&, const ParserT&, const boost::spirit::classic::space_parser&) [with IteratorT = __gnu_cxx::__normal_iterator<const char*, std::basic_string<char, std::char_traits<char>, std::allocator<char> > >, ParserT = boost::spirit::classic::rule<boost::spirit::classic::nil_t, boost::spirit::classic::nil_t, boost::spirit::classic::nil_t>]::scanner_t, DerivedT = boost::spirit::classic::rule<boost::spirit::classic::nil_t, boost::spirit::classic::nil_t, boost::spirit::classic::nil_t>, EmbedT = const boost::spirit::classic::rule<boost::spirit::classic::nil_t, boost::spirit::classic::nil_t, boost::spirit::classic::nil_t>&, T0 = boost::spirit::classic::nil_t, T1 = boost::spirit::classic::nil_t, T2 = boost::spirit::classic::nil_t]' /usr/include/boost/spirit/home/classic/core/scanner/impl/skipper.ipp:133: instantiated from 'static boost::spirit::classic::parse_info<IteratorT> boost::spirit::classic::impl::phrase_parser<boost::spirit::classic::space_parser>::parse(const IteratorT&, const IteratorT&, const ParserT&, const boost::spirit::classic::space_parser&) [with IteratorT = __gnu_cxx::__normal_iterator<const char*, std::basic_string<char, std::char_traits<char>, std::allocator<char> > >, ParserT = boost::spirit::classic::rule<boost::spirit::classic::nil_t, boost::spirit::classic::nil_t, boost::spirit::classic::nil_t>]' /usr/include/boost/spirit/home/classic/core/scanner/impl/skipper.ipp:155: instantiated from 'boost::spirit::classic::parse_info<IteratorT> boost::spirit::classic::parse(const IteratorT&, const IteratorT&, const boost::spirit::classic::parser<DerivedT>&, const boost::spirit::classic::parser<SkipT>&) [with IteratorT = __gnu_cxx::__normal_iterator<const char*, std::basic_string<char, std::char_traits<char>, std::allocator<char> > >, ParserT = boost::spirit::classic::rule<boost::spirit::classic::nil_t, boost::spirit::classic::nil_t, boost::spirit::classic::nil_t>, SkipT = boost::spirit::classic::space_parser]' test.cxx:15: instantiated from here /usr/include/boost/spirit/home/classic/core/non_terminal/impl/rule.ipp:191: error: no matching function for call to 'boost::spirit::classic::impl::abstract_parser<boost::spirit::classic::scanner<const char*, boost::spirit::classic::scanner_policies<boost::spirit::classic::iteration_policy, boost::spirit::classic::match_policy, boost::spirit::classic::action_policy> >, boost::spirit::classic::nil_t>::do_parse_virtual(const boost::spirit::classic::scanner<__gnu_cxx::__normal_iterator<const char*, std::basic_string<char, std::char_traits<char>, std::allocator<char> > >, boost::spirit::classic::scanner_policies<boost::spirit::classic::skipper_iteration_policy<boost::spirit::classic::iteration_policy>, boost::spirit::classic::match_policy, boost::spirit::classic::action_policy> >&)' /usr/include/boost/spirit/home/classic/core/non_terminal/impl/rule.ipp:215: note: candidates are: typename boost::spirit::classic::match_result<ScannerT, AttrT>::type boost::spirit::classic::impl::abstract_parser<ScannerT, AttrT>::do_parse_virtual(const ScannerT&) const [with ScannerT = boost::spirit::classic::scanner<const char*, boost::spirit::classic::scanner_policies<boost::spirit::classic::iteration_policy, boost::spirit::classic::match_policy, boost::spirit::classic::action_policy> >, AttrT = boost::spirit::classic::nil_t] |
一般來說如果一個未開發或開發中國家的國民遇到這種錯誤訊息,
可能幾十萬個裡面才會有一個人認真去看它是在做什麼;
不過倒是十個裡面會有七八個看都不看直接改成這樣:
1 2 3 4 |
rule<> r = +hex_p; string str("ffff 1234 abcde"); parse(str.c_str(), r, space_p); |
但是非常遺憾。
這樣雖然會通過編譯,
但是結果會是錯的。
如果 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 出來:
1 2 3 4 |
typedef scanner< string::const_iterator, scanner_policies<skipper_iteration_policy<> > > string_phrase_scanner_t; |
然後用 rule<string_phrase_scanner_t> r; 來定義 rule 就沒問題了;
不過因為 IteratorT 填的是 string::const_iterator,
所以照原本的 code 那樣寫也還是會出現 error,
但是正常人應該會把上面那種 code 整理成一個 function:
1 2 3 4 5 6 7 8 9 |
void foo(const string &str) { rule<string_phrase_scanner_t> r = +hex_p; if(parse(str.begin(), str.end(), r, space_p).full) { ... } ... } |
所以它先天上就一直會是 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 這個高科技兵器,
問題也能迎刃而解了:
1 2 3 4 |
auto r = +hex_p; string str("ffff 1234 abcde"); parse(str.begin(), str.end(), r, space_p); |