Почему сообщения об ошибках шаблона C ++ настолько ужасны?

28

Шаблоны C ++ известны тем, что генерируют длинные нечитаемые сообщения об ошибках. У меня есть общее представление о том, почему сообщения об ошибках шаблона в C ++ такие плохие. По сути, проблема в том, что ошибка не вызывается до тех пор, пока компилятор не обнаружит синтаксис, который не поддерживается определенным типом в шаблоне. Например:

template <class T>
void dosomething(T& x) { x += 5; }

Если оператор Tне поддерживает +=, компилятор сгенерирует сообщение об ошибке. И если это происходит где-то глубоко внутри библиотеки, сообщение об ошибке может быть длиной в тысячи строк.

Но шаблоны C ++, по сути, всего лишь механизм для утки во время компиляции. Ошибка шаблона C ++ концептуально очень похожа на ошибку типа времени выполнения, которая может возникнуть в динамическом языке, таком как Python. Например, рассмотрим следующий код Python:

def dosomething(x):
   x.foo()

Здесь, если xнет foo()метода, интерпретатор Python генерирует исключение и отображает трассировку стека вместе с довольно четким сообщением об ошибке, указывающим на проблему. Даже если ошибка не сработает до тех пор, пока интерпретатор не окажется глубоко внутри какой-либо библиотечной функции, сообщение об ошибке времени выполнения все равно не так плохо, как нечитаемая рвота, извергаемая типичным компилятором C ++. Так почему же компилятор C ++ не может быть более ясным о том, что пошло не так? Почему некоторые сообщения об ошибках шаблона C ++ буквально вызывают прокрутку окна моей консоли более 5 секунд?

Channel72
источник
6
Некоторые компиляторы имеют ужасные сообщения об ошибках, но другие действительно хороши ( clang++подмигнул).
Бенджамин Банье
2
Таким образом, вы бы предпочли, чтобы ваши программы терпели неудачу во время выполнения, поставлялись от рук клиента, а не во время компиляции?
П Швед
13
@ Павел, нет. Этот вопрос не о преимуществах / недостатках времени выполнения по сравнению с проверкой ошибок времени компиляции.
Channel72
1
В качестве примера больших ошибок в шаблонах C ++, FWIW: codegolf.stackexchange.com/a/10470/7174
kebs

Ответы:

28

Шаблонные сообщения об ошибках могут быть печально известными, но далеко не всегда длинными и нечитаемыми. В этом случае полное сообщение об ошибке (из gcc):

test.cpp: In function void dosomething(T&) [with T = X]’:
test.cpp:11:   instantiated from here
test.cpp:6: error: no match for operator+=’ in x += 5

Как и в вашем примере с Python, вы получаете «трассировку стека» точек создания шаблона и четкое сообщение об ошибке, указывающее на проблему.

Иногда сообщения об ошибках, связанных с шаблоном, могут быть намного длиннее по разным причинам:

  • «Трассировка стека» может быть намного глубже
  • Имена типов могут быть намного длиннее, поскольку шаблоны создаются с другими экземплярами шаблонов в качестве аргументов и отображаются со всеми их квалификаторами пространства имен
  • При сбое разрешения перегрузки сообщение об ошибке может содержать список возможных перегрузок (каждый из которых может содержать очень длинные имена типов)
  • Об одной и той же ошибке можно сообщать много раз, если во многих местах создается неправильный шаблон

Основным отличием от Python является статическая система типов, что приводит к необходимости включения (иногда длинных) имен типов в сообщение об ошибке. Без них иногда было бы очень трудно диагностировать, почему не удалось разрешить перегрузку. С ними ваша задача больше не угадать, где проблема, а расшифровать иероглифы, которые говорят вам, где она.

Кроме того, проверка во время выполнения означает, что программа остановится при первой обнаруженной ошибке, отображая только одно сообщение. Компилятор может отображать все ошибки, с которыми он сталкивается, пока он не сдастся; по крайней мере в C ++ он не должен останавливаться на первой ошибке в файле, поскольку это может быть следствием более поздней ошибки.

Майк Сеймур
источник
4
Не могли бы вы привести пример ошибки, являющейся следствием более поздней ошибки?
Руслан
12

Вот несколько очевидных причин:

  1. История. Когда gcc, MSVC и т. Д. Были новыми, они не могли позволить себе использовать много дополнительного пространства для хранения данных, чтобы создавать лучшие сообщения об ошибках. Памяти было недостаточно, чтобы они просто не могли.
  2. В течение многих лет потребители игнорировали качество сообщений об ошибках, поэтому поставщики, в основном, тоже.
  3. С некоторым кодом компилятор может повторно синхронизировать и диагностировать реальные ошибки позже в коде. Ошибки в шаблонах каскадируются так сильно, что все, что за первым, почти всегда бесполезно.
  4. Общая гибкость шаблонов затрудняет догадку, что вы, вероятно, имели в виду, когда в вашем коде есть ошибка.
  5. Внутри шаблона значение имени зависит как от контекста шаблона, так и от контекста создания экземпляра, а поиск в зависимости от аргумента может добавить еще больше возможностей.
  6. Перегрузка функций может предоставить множество кандидатов на то, на что может ссылаться конкретный вызов функции , и некоторые компиляторы (например, gcc) покорно перечисляют их все, когда есть неоднозначность.
  7. Многие программисты, которые никогда бы не подумали об использовании нормальных параметров, не убедившись, что переданные значения соответствуют требованиям, даже не пытаются проверять параметры шаблона вообще (и я должен признаться, я склонен к этому сам).

Это далеко не исчерпывающий, но вы получите общее представление. Даже если это не так просто, большинство из них можно вылечить. В течение многих лет я говорил людям, чтобы получить копию Comeau C ++ для регулярного использования; Я, вероятно, сохранил достаточно от одного сообщения об ошибке один раз, чтобы заплатить за компилятор. Теперь Clang добирается до той же точки (и это даже дешевле).

Я закончу общим наблюдением, которое звучит как шутка, но на самом деле это не так. Честно говоря, настоящая работа компилятора - превращать исходный код в сообщения об ошибках. Пришло время, чтобы поставщики сконцентрировались на том, чтобы выполнять эту работу немного лучше - хотя я открыто признаю, что когда я писал компиляторы, у меня была сильная тенденция относиться к нему как к вторичному (в лучшем случае), а в некоторых случаях почти игнорировал его полностью.

Джерри Гроб
источник
9

Простой ответ заключается в том, что Python был разработан для такой работы, тогда как многие вещи, связанные с шаблонами, возникли случайно. Например, она никогда не предназначалась для того, чтобы стать полной по Тьюрингу системой. И если вы не можете сознательно планировать и рассуждать о том, что происходит, когда ваша система работает , зачем кому-то ожидать тщательного, вдумчивого планирования того, что происходит, когда что-то идет не так?

Кроме того, как вы указали, интерпретатор Python может упростить вам задачу, отображая трассировку стека, поскольку он интерпретирует код Python. Если компилятор C ++ обнаружит ошибку шаблона и выдаст трассировку стека, это будет так же бесполезно, как и «шаблонная рвота», не так ли?

Мейсон Уилер
источник