Немножко лирики
Должен сразу предупредить, что эта тема вряд ли пригодится программистам, которые дружат с С++ давно и прочно. Моя цель — немного помочь тем, кто при использовании чужих библиотек с большим количеством template'ов впадает в легкую панику при виде сообщений об ошибках. Как показывает практика, зачастую всё совсем не так страшно, как выглядит и вполне помогает понять, в чём проблема.
Идея написать подобную заметку возникла в процессе написания программы, активно использующей Boost::Spirit2 — сложная грамматика, обилие semantic action, создание AST через attribute value и тому подобные радости. В какой-то момент я обратил внимание на то, что для исправления многих ошибок мне хватает одной-двух минут, в то время как в начале работы над проектом в аналогичных случаях на разбор ситуации могло уходить до часа.
Всё написано исходя из работы с gcc, но, думаю, провести аналогию с испольуземым %compiler_name& будет не сложно.
Самое время делиться!
Что же нам поможет?
Fatal Errors
Прежде всего, стоит включить опцию компилятора, делающую любую ошибку фатальной. В случае gcc это -Wfatal-errors. В случае метапрограммирования изучение более одного сообщения об ошибке за раз всё равно не имеет особого смысла — скорее всего исправление первой из них повлияет и на множество последующих. А стена текста станет куда ниже.
Зрить в корень
Если присмотреться, то окажется, что сообщения о template ошибках имеют примерно такую структуру:
In file included from %header path%, from %header path%, ... from %source filename%: %header path%: In function '%full name%' %header path%: instantiated from '%full name%' ... %source filename%: instantiated from %full name% ... %header path%: error: %error description%
А если присмотреться ещё внимательнее, то окажется, что блок с «instantiated from», проходящий по всей иерархии включаемых заголовочных файлов — самый объемный и самый бесполезный. Едва ли нас часто интересует, какими окольными путями дело дошло от вызова библиотечной функции до ошибки. Зато интересует, что это за ошибка и какая именно строчка нашего кода (блок «instantiated from» для %source filename%) её породила.
А это совсем немного и вполне читаемо. Иногда достаточно просто взглянуть на строчку в коде, из-за которой всё понеслось и сразу становится видна глупейшая опечатка, вылившаяся в подобное непотребство. Не придётся даже читать сообщение об ошибке, которой в такой ситуации всё равно ничего толкового не скажет.
typedef есть typedef
Удобно иметь под рукой набор find-and-replace правил для основных typedef типов, используемых в проблемном коде. Даже короткий std::basic_string<char> куда лучше смотрится в виде std::string, что уж говорить о векторах списков пар векторов и очередей. Например, в vim можно написать простой replace-скрипт, через который прогонять каждый раз сообщение об ошибке, если оно длиннее пары строк, и приводить в читаемый вид.
Чтение исходников
Если ошибка в template коде, то мы практически в шаге от исходников. Когда уже нет ни малейших идей, в чём может быть проблема, стоит открыть ту часть заголовочного файла библиотеки, на которую жалуется компилятор ( %header path%: error: ) и познакомиться поближе. Как правило, даже в очень жестоких boost библиотеках небольшие кусочки по отдельности читаемы и понятны.
После того, как я перестал в ужасе смотреть на очередное сообщение об ошибке, а стал сразу же препарировать этими простыми способами, время отладки сократилось на порядок. Чего и вам желаю.
А какие трюки используете вы? Был бы рад прочитать в комментариях советы по теме. Предлагаю бороться с недостатками дизайна С++ коллективно.