Подтвердить что ты не робот

С++ против D, Ada и Eiffel (ужасные сообщения об ошибках с шаблонами)

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

Мне интересно, эта проблема распространена для всех языков, поддерживающих общее программирование? Или что-то не так с шаблонами С++?

К сожалению, я не знаю никаких других языков, поддерживающих универсальное программирование (обобщенные версии Java и С# слишком упрощены и не так сильны, как С++-шаблоны).

Итак, я спрашиваю вас, ребята: D, Ada, Eiffel шаблоны (generics), производящие такие уродливые сообщения об ошибках тоже? И возможно ли иметь язык с мощной универсальной парадигмой программирования, но без уродливых сообщений об ошибках? И если да, то как эти языки решают эту проблему?

Изменить: для downvoters. Я действительно люблю С++ и шаблоны. Я не говорю, что шаблоны плохие. На самом деле я большой поклонник общего программирования и метапрограммирования шаблонов. Я просто спрашиваю, почему я получаю такие уродливые сообщения об ошибках от компиляторов.

4b9b3361

Ответ 1

Проблема в том, что устранение ошибок затруднено, независимо от контекста.

И когда вы учитываете ужасные грамматики C и С++, вы можете только удивляться, что сообщения об ошибках не хуже этого! Я боюсь, что грамматика C была разработана людьми, которые не имели понятия о существенных свойствах грамматики, одна из которых заключалась в том, что чем меньше полагалось на контекст, тем лучше, а другое - на то, что вы должны стремиться сделать это как можно более однозначное.

Проиллюстрируем общую ошибку: забываем о запятой.

struct CType {
  int a;
  char b;
}
foo
bar() { /**/ }

Хорошо, так это неправильно, где должна отсутствовать полуточка? Ну, к сожалению, это двусмысленно, оно может идти до или после foo, потому что:

  • C считает нормальным объявление переменной в шаге после определения struct
  • C считает нормальным не указывать тип возврата для функции (в этом случае она по умолчанию имеет значение int)

Если мы рассуждаем, мы могли бы видеть, что:

  • Если foo называет тип, то он относится к объявлению функции
  • если нет, это, вероятно, обозначает переменную... если, конечно, мы не сделали опечатку, и она должна была быть написана fool, которая является типом:/

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

Похоже, что люди, работающие над gcc, больше заинтересованы в создании быстрого кода (и я имею в виду быстрый поиск новых тестов на gcc 4.6) и добавление интересных функций (gcc уже реализует большинство - если не всех - С++ 0x), чем создание легко читаемых сообщений об ошибках. Можете ли вы обвинить их? Я не могу.

К счастью, есть люди, которые считают, что точная отчетность об ошибках и хорошее восстановление ошибок - очень достойная цель, и некоторые из них уже довольно много работают над CLang, и они продолжают это делать.

Некоторые приятные функции, с головы:

  • Тесные, но полные сообщения об ошибках, которые включают в себя диапазоны источников, чтобы точно указать, где ошибка, исходящая из
  • Fix-It отмечает, когда это очевидно, что означало
  • В этом случае компилятор разбирает остальную часть файла, как если бы исправление уже было там, вместо того, чтобы извергать строки по строкам тарабарщины
  • (недавний) избегать включения стека включения для заметок, вырезания на крутизне
  • (недавний), пытаясь разоблачить типы параметров шаблона, которые на самом деле написал разработчик, и сохранение typedefs (таким образом говоря о std::vector<Name> вместо std::vector<std::basic_string<char, std::allocator<char>>, std::allocator<std::basic_string<char, std::allocator<char>> >, что делает все возможное)
  • (последнее) правильно восстанавливается в случае отсутствия template в случае отсутствия в вызове метода шаблона из другого метода шаблона

Но каждый из них потребовал от нескольких часов до нескольких дней работы.

Они, конечно, не пришли бесплатно.

Теперь концепции должны (как правило) сделать нашу жизнь проще. Но они были в основном непроверены, поэтому было сочтено предпочтительным удалить их из проекта. Должен сказать, я рад этому. Учитывая относительную инерцию С++, лучше не включать функции, которые не были тщательно пересмотрены, и концептуальные карты меня действительно не волновали. Они также не восхищались Бьярне или Хербом, поскольку они говорили, что они будут переосмысливать концепции с нуля для следующего стандарта.

Ответ 2

В общем, я обнаружил, что сообщения об ошибках компилятора Ada для дженериков действительно не намного труднее читать, чем любые другие сообщения об ошибках компилятора Ada.

Сообщения об ошибках шаблонов С++, с другой стороны, известны тем, что были романы ошибок. Основное различие, на мой взгляд, заключается в том, как С++ создает экземпляр шаблона. Дело в том, что С++-шаблоны гораздо более гибкие, чем дженерики Ada. Он настолько гибкий, что почти как препроцессор макросов. Умные люди в Boost использовали это для реализации таких вещей, как лямбды и даже целые другие языки.

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

В Ada Generics на самом деле строго типизированы и предоставляют полную информацию, скрывающуюся для клиента, как и обычные пакеты и подпрограммы. Поэтому, если вы получите сообщение об ошибке, оно обычно ссылается только на один общий тип, который вы пытаетесь установить, а не на всю иерархию, используемую для его реализации.

Итак, да, сообщения об ошибках шаблонов С++ хуже, чем у Ada.

Теперь отладка - это совсем другая история...

Ответ 3

D имеет две функции для улучшения качества сообщений об ошибках шаблона: Ограничения и static assert.

// Use constraints to only allow a function to operate on random access 
// ranges as defined in std.range.  If something that doesn't satisfy this
// is passed, the compiler will error before even trying to instantiate
// fun().
void fun(R)(R range) if(isRandomAccessRange!(R)) {
    // Do stuff.
}


// Use static assert to check a high level invariant.  If 
// the predicate is false, the error message will be 
// printed and compilation will stop before a screen 
// worth of more confusing errors are encountered.
// This function takes any number of ranges to merge sort
// and the same number of temporary buffers to merge into.
void mergeSort(R...)(R ranges) {
    static assert(R.length % 2 == 0, 
        "Must have equal number of ranges to be sorted and temporary buffers.");

    static assert(allSatisfy!(isRandomAccessRange, R), 
        "All arguments to mergeSort must be random access ranges.");

    // Implementation
}

Ответ 4

В статье Generic Programming описывается множество плюсов и минусов дженериков на нескольких языках, включая Ada в частности. Несмотря на отсутствие специализации шаблонов, все Ada generic экземпляры являются эквивалентными к объявлению экземпляра... сразу после этого следует тело экземпляра ". Как правило, сообщения об ошибках имеют тенденцию возникать во время компиляции, и они обычно представляют собой знакомые нарушения безопасности типов.

Ответ 5

Eiffel имеет лучшее из всех сообщений об ошибках, потому что он имеет лучшую из всех систем шаблонов. Он полностью интегрирован в язык и хорошо работает, потому что он является единственным языком, который использует аргументы в аргументах.

Поэтому это гораздо больше, чем простая копия и вставка компилятора. К сожалению, объяснение разницы в нескольких строках невозможно. Просто пойдите и посмотрите на EiffelStudio.

Ответ 6

Есть некоторые попытки улучшить сообщения об ошибках. Clang, например, уделяет большое внимание созданию более легко читаемых сообщений об ошибках компилятора. Я использую его только на короткое время, но мой опыт до сих пор был довольно положительным по сравнению с эквивалентными ошибками GCC.