Pull to refresh
19
0
Дмитрий @dkozh

Программист

Send message
ошибки в хедере, и создание преамбулы обламывается

По крайней мере, сейчас это не так. Там есть всякие досадные упущения (например, include not found считатется fatal error, а во время парсинга по коду расставлены на это оптимизации всякие — мол, после fatal error в режиме компиляции все равно не увидим), но все они решаемые, не принципиальные.

Интересная и уникальная возможность clangd на мой взгляд это индексация
во время компиляции

Это работает, если есть возможность пересадить пользователя IDE на нужный компилятор (причем, желательно, свежий, а еще и свой форк, а то иначе функциональность IDE начинает зависить от того, что приняли в upstream, а что еще не успели). Кроме Apple+Xcode (которые, собственно, этот index-while-building и сделали) и in-house разработки в Google (которые в основном вкладываются в clangd), ни у кого такой возможности нет (ну еще MSVC, но там отдельная история).

Спасибо за статью!


(disclamer: я — разработчик в команде CLion, но я не обязательно сейчас рассказываю про наши планы или транслирую мнение компании, просто абстрактно рассуждаю исходя из своего опыта).


Описанная картина довольно мрачная и примерно соответствует действительности, но некоторые факты позволяют смотреть в будущее более позитивно:


  • Поддержка IDE является одной из целей проекта, и, хотя пока она в достаточно примитивном состоянии, для нее заложен неплохой базис
  • Достаточно много проектов вкладывается в IDE-составляющую clang, и достаточно много людей работает над этим на постоянной основе
  • Если не использовать libclang (и не заботиться о том, чтобы работать со старыми стабильными версиями clang-а из коробки), то можно сделать достаточно много через C++ API уже сейчас.

В CLion 2018.2 мы включили подсветку ошибок через clang (по умолчанию в Linux и Mac, Windows на подходе), ограничимся мы ли только этим, или пойдем дальше — вопрос, требующий исследования, но смотрим мы на это все с осторожным оптимизмом.


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


При этом все те оптимизации, которые позволяют IDE быть быстрее компилятора — это же не магия какая-то, возможная потому, что мы в IDE — надо сделать все ровно то же самое, что и компилятор, только в большем объеме сложнее (всякие ленивости и инкрементальности не достаются бесплатно с точки зрения поддержки и количества багов). Сэкономить негде — это же C++, в нем любой пункт из стандарта может повлиять (и влияет) на что угодно — от индексации до форматирования.


Чуть-чуть по статье:


И если самописная его версия прекрасно "понимает", что имеет дело с полуфабрикатом, то вот clang — нет. И очень сильно удивляется. Что получится в результате такого удивления — зависит "от", как говорится.

Это не принципиальное ограничение компилятора, это вопрос качества восстановления от ошибок. Это больше искусство, чем наука, и во многих случаях clang работает хуже, чем, например, CLion (а в каких-то — лучше). Но "поругаться на ошибку и пойти дальше, предположив, что же имелось в виду" — базовая функциональность компилятора, и в clang тоже потрачено много усилий на это. IDE, конечно, может немножечко пожертвовать корректностью работы на правильном, но экзотическом коде, чтобы лучше восстанавливаться на недописанном, но неправильном, но вообще такие жертвы обычно выходят боком через какое-то время (когда в STL написали как раз то, про что мы год назад подумали "ну, так нормальные люди не пишут").


Первое, очевидное, и, на самом деле, единственное решение — это использовать динамически генерируемые precompiled headers.

И libclang, и clangd умеют работать с т. н. "preambles" — это автоматически генерируемые PCH для всей пачки #include-директив, которые идут в начале файла. Работает очень хорошо — если первая подсветка — примерно как компиляция, то последующее редактирование — гораздо приятнее.


Очевидный недостаток — поменял что-то в хедере — и вся преамбла инвалидировалась, перестраивай заново. В этом месте и находится пока что самый очевидный вектор атаки — сделать так, чтобы можно было переиспользовать куски AST из хедеров, которые были не затронуты изменением. Это сложно, это нужно долго исследовать, но если это сделать — жизнь начнется совсем другая. Благо, работа по реализации модулей — очень связана с этим (грубо говоря, надо динамически понять, можно ли использовать хедер так, как будто он был бы модулем), и тоже активно ведется.


Ну или можно подождать, когда все на модули перейдут, ха-ха.


Да, какие-то идеи про то, как должен работать C++-движок в IDE на основе clang реализовать невозможно, и есть вероятность, что "идеальная C++ IDE" должна работать на других принципах — если это так, то мы туда придем, а по дороге обеспечим (в том числе, и через clang) достойный user experience :)


С удовольствием могу ответить на какие-то более конкретные технические вопросы, если они возникнут.

Good news, everyone.


И хотя более новые версии GCC так (пока) не делают, такое поведение формально не противоречит стандарту, потому что в результате undefined behaviour может получиться все, что угодго (по факту оптимизатор решил, что (unsigned short *)&a и &a не алиасятся, и решил переставить инструкции местами, потому что почему бы и нет).

Как раз direct-initialization из N3912 я понимаю и поддерживаю.


А про range-based for — мне кажется, что добавить особый случай для braced-init-list именно в него было бы лучше, чем то, что получилось. Ну да это дело вкуса, видимо.

В C++14 — да, нужен move-constructor, причем его нужно явно написать (например, = default). В С++17, к счастью, это требование убрали, и все будет работать.

Да, я за то, чтобы вообще запретить выводить std::initializer_list для auto переменных, это и пытался изложить в статье.


Наверняка текущий странный вариант не так просто приняли, но причин я пока не понял. Все, что есть в n3922 — это параграф, который не объясняет проблем:


There was also support (though less strong) in EWG for an alternative proposal in which auto deduction does not distinguish between direct list-initialization and copy list-initialization, and which allows auto deduction from a braced-init-list only in the case where the braced-init-list consists of a single item. Possible wording was for that alternative was included in N3681 by Voutilainen.

Можно пойти еще дальше и вообще не вводить list-initialization в таком виде, а конструкторы вызывать всегда скобочками, например std::vector<int> v({1, 2, 3});.


Но тогда бы не были достигнуты другие цели:


  • Универсальность (иначе инициализация C-структур, масисвов и примитивных типов через фигурные скобки осталась бы странным частным случаем)
  • Защита от most vexing parse
  • Защита от narrowing conversions

Стоили ли эти цели того, что получилось? Насколько я понимаю, в сообществе нет консенсуса по этому поводу. Мое мнение я написал в статье — сама по себе list-initialization — ок, а вот правила про std::initializer_list получились не очень.


Хотя и Вашим бы предложением ничего страшного не случилось бы, как мне кажется.

Тогда я, пожалуй, прокомментирую, почему мне это не нравится :)


  • Это не очень консистентно: auto x = 5; и auto x(5); значат одно и то же, auto x = {5};и auto x{5}; — разное, а что делать с auto x({5}); — вообще не очень ясно
  • Такая запись прививает ложное чувство, что {1} — это выражение с типом std::initializer_list<int>, что не так. Это вообще не выражение, у него нет типа, а такое его использование с auto-переменными — отдельно прописанное исключение (второе такое же — это range-based for). Мне кажется, исключений в C++ и так достаточно :)
  • Практически никогда не требуется создавать локальную переменную типа std::initializer_list, а язык поощрает такое поведение

Кстати, обратите внимание, что N3922 — это defect report, и применяется не только в C++17, но и к предыдущим стандартам задним числом, что приводит к интересным результатам при апгрейде компилятора...

Да, я опечатался и перепутал в последнем предложении copy и direct, сейчас исправлю.

Пара фич, которые могут помочь (не решение, но возможный workaround):


  • В списках обычно работает live search, то есть если фокус на списке, то можно начать печатать подстроку или первые буквы из частей названия (отфильтруются все таргеты, кроме содержащих введенную подпоследовательность в названиии)
  • Action "Select Run/Debug configuration" можно назначить на хоткей, чтобы не кликать мышкой в combo-box, еще есть аналогичные экшены "Run..." и "Debug..." (выбрать конфигурацию и сразу запустить)
Никогда не забуду лицо своего товарища, изучавшего С++ в универе, когда я впервые показал ему функцию высшего порядка.

template<typename In>
auto twice(In x, int arg) {
    return [=] {
        x(arg);
        x(arg);
    };
}

— Не-не-не, Дэвид Блейн, это же шаблоны, как я это в библиотеку скомпилирую?


std::function<void()> twice(std::function<void(int)> x, int arg) {
    return [=] {
        x(arg);
        x(arg);
    };
}

Много чего плохо в C++, но с функциями высшего порядка вроде ОК :)

Извиняюсь, форматирование цитат пропало.

По-моему, все не так плохо. Ну или плохо, но виноват не комитет, а авторы предложений, которые не смогли довести их до ума, и остальное сообщество, которое сидело и терпеливо ждало, пока добрые дяди сделают хорошо, вместо конструктивного обсуждения.
string_view же выглядит как затычка, для тех, кто не освоил константные ссылки и shared_ptr
Когда на вход приходит гигабайт текста, который нужно сложным образом обработать, то без string_view есть два варианта — использовать во всех интерфейсах std::string и утонуть в бесконечных копированиях маленьких кусочков, или иметь в интерфейсе функции типа process(const char*, offset, length). string_view есть в бусте под именем boost::string_ref и это life saver.
«Не было консенсуса для принятия».
Унифицированный синтаксис вызова — ну правда же не все так однозначно. Все члены класса начинали бы участвовать в разрешении перегрузок наравне со свободными функциями, как будто сейчас правила разрешения перегрузок недостаточно сложные. Я так понял, что комитет побоялся, что старый код может тихо поменять семантику и продолжить компилироваться, и правильно сделал. Для примера — вот какой оптимистичный тон был у статьи в 2014 году: www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4174.pdf, и что с ней стало через год www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4474.pdf.
Комитет сказал «не готово»
Концепты не готовы. Возможно, спецификация достаточно хорошая, но мы этого не знаем, потому что существующие реализации не позволяют этого проверить, поскольку не умеют практически ничего: они не сообщают, почему именно тип не подошел под концепт, и не проверяют, что в для constrained-типа не используется чего-то, чего нет в концепте. Если вдруг окажется, что реализация этих вещей сопряжена с трудностями, то мы останемся с принятой, но невыполнимой спецификацией (export templates, помните?). Статья на тему: honermann.net/blog/?p=3, дискуссия: www.reddit.com/r/cpp/comments/49b0ph/why_concepts_didnt_make_c17. Обратите внимание на комментарии, где Andrew Sutton, автор спецификации и реализации в GCC, расстроен низкой активностью контрибьюторов:
You guys do know that GCC is an open source compiler. If you had wanted something different, you could have submitted a patch sometime in, say, the last 2 years.
Sadly, few such patches were received. I can count on one hand the number of people who have worked to make the implementation better. I cannot count the number of people who have lined up to throw stones.

Information

Rating
Does not participate
Location
Санкт-Петербург и область, Россия
Registered
Activity