Boost.Spirit та ітератори
Jul. 21st, 2011 09:11 pm![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
Припустимо, є такий код:
std::string str("1,2,3,4,5"); std::vector<int> v; ListGrammar<std::string::const_iterator> grammar; if (qi::parse(str.begin(), str.end(), grammar, v)) { // do something }
З першого погляду тут одна помилка: qi::parse приймає посилання на ітератори, а не значення. Так, принаймні, написано у документації: Iterator Based Parser API. Це зроблено для того щоб після розбору отримати позицію останнього розібраного символу. Та іноді позиція нам не потрібна, тому розробники Boost.Spirit додали overloaded функцію, що приймає обидва ітератора по константному посиланню (уся її суть у тому що там локально створюється копія ітератора і віддається звичайній функції). Виходить, помилок немає?
Компілятори так не вважають. Найкращий вивід у шланга (хоча gcc і ekopath теж нормально показують, просто кольором не виділяють і стрілки не малюють):
... /usr/include/boost/spirit/home/qi/reference.hpp:43:30: error: no matching member function for call to 'parse' return ref.get().parse(first, last, context, skipper, attr); ~~~~~~~~~~^~~~~ ... /usr/include/boost/spirit/home/qi/nonterminal/rule.hpp:247:14: note: candidate function [with Context = const boost::spirit::unused_type, Skipper = boost::spirit::unused_type, Attribute = std::vector<int, std::allocator<int> >] not viable: no known conversion from '__gnu_cxx::__normal_iterator<char *, std::basic_string<char> >' to '__gnu_cxx::__normal_iterator<const char *, std::basic_string<char> > &' for 1st argument bool parse(Iterator& first, Iterator const& last ^ /usr/include/boost/spirit/home/qi/nonterminal/rule.hpp:293:14: note: candidate function template not viable: requires 6 arguments, but 5 were provided bool parse(Iterator& first, Iterator const& last
Дивимось у rule.hpp - overloaded метод parse: на 5 і 6 параметрів. Ну, той що на 6 нас не цікавить, а от чому не вийшло з тим що на п’ять?
Сигнатура:
template <typename Context, typename Skipper, typename Attribute> bool parse(Iterator& first, Iterator const& last , Context& /*context*/, Skipper const& skipper , Attribute& attr) const
Дивимось уважно на те чому він не підійшов: "no known conversion from '__gnu_cxx::__normal_iterator<char *, std::basic_string<char> >' to '__gnu_cxx::__normal_iterator<const char *, std::basic_string<char> > &' for 1st argument". Що? Немає методу для перетворення value у referense? Так у нас тут уже referense в усіх полях!
Години 3 у мене пішло на те щоб побачити де помилка. Саме смішне, що у цьому повідомленні вона чітко описана :)
А все тому що я спочатку не знав про overloaded версію qi::parse, виніс два ітератори і передав їх по посиланню:
std::string str("1,2,3,4,5"); std::string::const_iterator from(str.begin()); std::string::const_iterator to(str.end()); std::vector<int> v; ListGrammar<std::string::const_iterator> grammar; if (qi::parse(from, to, grammar, v)) { // do something }
Та хоч воно і зібралось після цього, помилка не давала мені спокою.
Все просто. Дивимось початковий код. Яким типом інстанційовано граматику? Правильно, std::string::const_iterator. Власне, це const char *, як правильно нам показує компілятор ekopath. А що ми передаємо у qi::parse? Правильно, begin() і end() які мають дві версії: з isterator і з const_itaretor. Яка версія буде вибрана? Дивимось на сигнатуру:
iterator begin(); const_iterator begin() const;
У нас str константна? Ні. Які тоді питання? Туди передається звичайний ітератор. Про що чітко пише компілятор: "no known conversion from '__gnu_cxx::__normal_iterator<char *, std::basic_string<char> >' to '__gnu_cxx::__normal_iterator<const char *, std::basic_string<char> > &' for 1st argument". Та хто ж його читає...
Найкраще, мабуть, робити як самі бустівці: писати шаблонну функцію-обгортку над qi::parse, параметризовану типом ітератора. Якось так:
templatebool parse_int(const Iterator & from, const Iterator & to, std::vector<int> & v) { return qi::parse(from, to, ListGrammar<Iterator>(), v); }
В процесі битви з компіляторами я знайшов щонайменше 3 виходи із ситуації (константний std::string, ітератори окремо і через шаблонну функцію), але шило в жопі не давало покинути проблему не розібравши її до кінця :). Да, константний std::string - останнє рішення, яке просто підтвердило гіпотезу про проблеми з константністю.
PS: обожнюю розставляти < і > замість < і > :(
PPS: а ще я сьогодні поборов скіпери, так що тепер з чистою совістю буду дивитись кіна під червоного вина :)