Чому повідомлення про помилки шаблону 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 секунд?


6
Деякі компілятори мають жахливі повідомлення про помилки, але інші справді хороші ( clang++підморгують).
Бенджамін Баньє

2
Отже, ви б хотіли, щоб ваші програми виходили з ладу під час виконання, відправлялися в руки клієнта, а не під час компіляції?
П Швед

13
@Pavel, ні. Це питання не стосується переваг / недоліків часу виконання та порівняння перевірки помилок у часі.
Канал72

1
Як приклад великих помилок шаблону C ++, FWIW: codegolf.stackexchange.com/a/10470/7174
кеби

Відповіді:


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 ++ для регулярного використання; Я, ймовірно, один раз врятував одне повідомлення про помилку, щоб заплатити за компілятор. Тепер Кланг добирається до тієї ж точки (і це навіть дешевше).

Я закінчу загальним спостереженням, яке звучить як жарт, але насправді це не так. Велика частина часу, реальна робота компілятора чесно це перетворити вихідний код в повідомлення про помилки. Давно пора продавці зосередитись на тому, щоб виконувати цю роботу трохи краще - хоча я відкрито визнаю, що коли я писав компілятори, у мене була сильна тенденція ставитися до неї як до вторинної (у кращому випадку), а в деяких випадках майже ігнорувала її повністю.


9

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

Крім того, як ви вказали, інтерпретатор Python може зробити вас набагато простіше, відображаючи слід стека, оскільки він інтерпретує код Python. Якщо компілятор C ++ потрапляє в помилку шаблону і дає вам стеження за стеком, це було б так само не корисно, як "блювота з шаблоном", чи не так?

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.