LINUX.ORG.RU

C++ modules и системы сборки

 , ,


1

4

Кто как собирает проекты с использованием C++ modules?

Компиляторы уже давно умеют их, например, в clang даже объявили deprecated опцию -fmodules-ts и поддержка модулей автоматически включается при активации стандарта C++20.

Однако, собирать руками проекты удовольствия мало. Хочется использовать какую-нибудь систему сборки. И вот тут возникает проблема. Самая популярная система сборки в мире C/C++ - CMake - поддерживает модули только для MSVC, что как бы не очень интересно (так как ограничивает одной платформой, к тому же clang/gcc по моим тестам обычно генерируют более оптимальный код, чем MSVC, даже под Windows). Какие есть альтернативы? Можно ли приобщиться к модулям не путём написания вручную Makefile или Bash-скриптов?

★★★★★

Последнее исправление: hobbit (всего исправлений: 3)
Ответ на: комментарий от eao197

В C++ нужно суметь засунуть в один модуль код, который располагается в нескольких файлах

Зачем? Почему не раскидать этот код по более мелким модулям? Модули в C++ не могут подключать в себя другие модули?

А почему придумывают что-то сложное при наличии кучи примеров давно существщих простых решений, действительно вопрос.

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

grem ★★★★★
()
Последнее исправление: grem (всего исправлений: 1)
Ответ на: комментарий от eao197

любой компилятор будет падать

А почему? Вроде ж не так давно вообще была какая-то движуха, чтобы сливать весь проект в один файл для увеличения скорости сборки.

GAMer ★★★★★
()
Ответ на: комментарий от grem

Зачем?

Чтобы можно было писать import std; а не выписывать кучу более мелких импортов.

Почему не раскидать этот код по более мелким модулям?

Потому что для многих библиотек это было бы гораздо проще и логичнее.

eao197 ★★★★★
()
Ответ на: комментарий от GAMer

А почему?

А это у разработчиков компиляторов нужно спросить, почему у них время от времени на сложном коде internal compiler error случаются.

eao197 ★★★★★
()
Ответ на: комментарий от GAMer

А почему? Вроде ж не так давно вообще была какая-то движуха, чтобы сливать весь проект в один файл для увеличения скорости сборки.

Там определенные пределы есть для слияния.

x86 версии компилятора имеют лимит памяти в 3 gb.

Например, вот этот тест https://github.com/Microsoft/STL/blob/main/tests/std/tests/P0088R3_variant/test.cpp требует 3,033,104,384 байт для компиляции x86 кода с помощью cl.exe

fsb4000 ★★★★★
()
Ответ на: комментарий от eao197

import std

Тогда уж сразу «include std», чего мелочиться то?

для многих библиотек это было бы гораздо проще и логичнее.

Решается импортом модулей внутрь модулей. Но смотря что имеется ввиду. Многие библиотеки тоже много из чего состоят и делать import boost было б чрезмерным.

grem ★★★★★
()
Последнее исправление: grem (всего исправлений: 1)
Ответ на: комментарий от eao197

Да и более того, имхо, C++ с модулями расколет C++ на много лет так же, как это было с Python2/Python3.

Я помню, Дедфуд начал лямбды в личкрафт тащить, как только они в gcc появились, я их впервые увидел, когда на моём дебиане личкрафт отказался собираться, что это, думаю, за хрень в квадратных скобках. Но то лямбды, а тут изменение куда более масштабное…

hobbit ★★★★★
()
Ответ на: комментарий от eao197

модули-то – это не заголовочные файлы, там же еще и реализация. А иметь один файл со 100500 строк реализации в C++ так себе идея

Вот тут вообще не понял. При сборке модуля создаётся объектный файл. Чем это будет отличаться от сборки посто файла в те же 100500 строк? Сомнительна логичность иметь всё внутри одного файла. Такие крупные библиотеки обычно имеют кучу внутренних функций, которые наружу не высовываются и внутри этого же модуля содержаться не обязаны.

grem ★★★★★
()
Последнее исправление: grem (всего исправлений: 1)
Ответ на: комментарий от grem

Тогда уж сразу «include std», чего мелочиться то?

Вы специально путаете include с import? Или на самом деле разницу не понимаете?

Решается импортом модулей внутрь модулей.

Что вы под этим понимаете? Чтобы модуль mod_a мог реэкспортировать содержимое mod_b?

Многие библиотеки тоже много из чего состоят и делать import boost было б чрезмерным.

Boost – это вырожденный случай. Это не библиотека, а скопище библиотек.

А вот какая-нибудь библиотека на 50-100KLOC, решающая всего одну задачу, вполне может быть представлена единственным модулем. Делаешь просто import botan; и не паришься на тему того, что там внутри.

eao197 ★★★★★
()
Ответ на: комментарий от Lrrr

Скорость сборки? Так это решается рефакторингом, вон в ядре линукс сейчас этим занимаются.

В плюсах это рефакторингом не решается, потому что шаблоны.

Видимость символов и так далее? В сишке это уже сейчас можно вертеть как угодно без всяких модулей,

Нельзя.

hateyoufeel ★★★★★
()
Ответ на: комментарий от grem

При сборке модуля создаётся объектный файл.

Насколько я могу судить по документации clang-а, там все несколько сложнее:

# Precompiling the module
$ clang++ -std=c++20 interface_part.cppm --precompile -o M-interface_part.pcm
$ clang++ -std=c++20 impl_part.cppm --precompile -fprebuilt-module-path=. -o M-impl_part.pcm
$ clang++ -std=c++20 M.cppm --precompile -fprebuilt-module-path=. -o M.pcm
$ clang++ -std=c++20 Impl.cpp -fmodule-file=M.pcm -c -o Impl.o

# Compiling the user
$ clang++ -std=c++20 User.cpp -fprebuilt-module-path=. -c -o User.o

# Compiling the module and linking it together
$ clang++ -std=c++20 M-interface_part.pcm -c -o M-interface_part.o
$ clang++ -std=c++20 M-impl_part.pcm -c -o M-impl_part.o
$ clang++ -std=c++20 M.pcm -c -o M.o
$ clang++ User.o M-interface_part.o  M-impl_part.o M.o Impl.o -o a.out

Чем это будет отличаться от сборки посто файла в те же 100500 строк?

Тем, что если модуль состоит из 500 файлов, то эти 500 файлов будут компилироваться в объектники отдельно, компилятору не нужно собирать из них один большой файл на 100500 строк.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Что вы под этим понимаете?

Понимаю импорт внутрь модуля из внешнего модуля. Без реимпорта дальше. Самый простой пример: библиотека реализующая интегрирование одномерной функции на заданном отрезке. Она предоставляет интерфейс функции, в которую передаётся f(x), концы отрезка a, b, как дополнительный параметр метод интегрирования. Реализации методов интегрирования пользователю не видны и импортируются внутрь модуля библиотеки из других модулей.

А вот какая-нибудь библиотека на 50-100KLOC, решающая всего одну задачу

Что же это за библиотека? Нет, я могу допустить, что внутри 1 функция на десятки тысяч строк, но зачем её такой делать? Экономия на вызовах?

grem ★★★★★
()
Ответ на: комментарий от grem

Понимаю импорт внутрь модуля из внешнего модуля.

Э… А какое это отношение имеет к противопоставлению:

import std;

и

import std.set;
import std.unordered_map;
impoer std.filesystem;

?

Речь ведь изначально шла о том, насколько просто (и возможно ли вообще) представить std в виде одного модуля.

Что же это за библиотека?

ICU или libiconv, к примеру. Парсер XML с поддержкой разной около-XML-ной лабуды.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Модуль не должен состоять из 500 файлов, а только из одного. Другие модули могут подключаться внутрь модулей для выполнения промежуточных задач и собираются отдельно, перед сборкой модуля или файла, который от них зависит. Никто из кучи модулей один объектный файл ни в одном языке не собирает.

Для примера, в фортране: из файла исходного кода модуля собирается объектный файл .o, который потом компонуется в файл mod. Эти файлы дальше используются при дальнейшей компиляции зависящих от них файлов. То есть они ищутся как хдеры, потому что в них содержится в том числе и информация об интерфейсах.

То есть если у нас есть основной файл и два файла модуля, то в результате сборки появятся 6 дополнительных файла: 3 объектных файла, 2 файла .mod, исполняемый файл. Разве в C++ не так? Вроде так же.

grem ★★★★★
()
Ответ на: комментарий от grem

Модуль не должен состоять из 500 файлов, а только из одного.

Почему?

Никто из кучи модулей один объектный файл ни в одном языке не собирает.

Мы говорим про C++, здесь многое не так, как в других языках.

Для примера, в фортране

Примеры из фортрана в разговоре про модули в C++ не нужны.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Никому не нужно. Те же строки всегда подключали явно не как #include <std>. Так что и какой-нибудь import std_string не перетрудятся написать.

grem ★★★★★
()
Ответ на: комментарий от eao197

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

grem ★★★★★
()
Последнее исправление: grem (всего исправлений: 1)
Ответ на: комментарий от grem

Никому не нужно.

Отучаемся говорить за всех (c)

Те же строки всегда подключали явно не как #include <std>

Во-первых, тогда других способов не было.

Во-вторых, вынужден повторить вопрос: а вы точно понимаете разницу между include и import?

Так что и какой-нибудь import std_string не перетрудятся написать.

Видите ли, в чем дело: возможность поместить несколько .cpp в один модуль никак не лишает вас права создавать мелкие пакеты, вроде grem.calc.approximation или grem.calc.interpolation. При этом оставляет возможность сделать всего один модуль icu.

А вот обратное заставит всех трахаться с мелкими модулями. Вне зависимости от половых предпочтений.

eao197 ★★★★★
()
Ответ на: комментарий от peregrine

Неа. std::from_chars до сих пор для float работает только на MSVC. Почему? Потому что никто (в GCC и clang) не хочет писать реализацию с нуля, а использование strtof требует аллокации (чтобы приписать \0) запрещённой стандартом (и здравым смыслом). То есть они 5 лет не могут написать конвертер строки во float. Да, там есть нюансы, чтобы не было потерь точности, но алгоритму много лет и можно адаптировать кусок из strtof, который уже есть у обоих компиляторов и, очевидно, должен иметь совместимую лицензию.

Как следствие, std::string_view нельзя конвертировать во float, подавай std::string. А что если ты вырезаешь подстроки из большой строки?

KivApple ★★★★★
() автор топика
Последнее исправление: KivApple (всего исправлений: 2)
Ответ на: комментарий от eao197

Отучаемся говорить за всех (c)

Да я и не собирался нести ответственность за желающих оттяпать себе пальцы лопатой.

Во-первых, тогда других способов не было.

Да, я понимаю разницу между include и import. В отличие от программирующих на C++ модули не впервые вижу. Поэтому не понимаю, почему то, что должно упростить, как это произошло в других языках, наоборот всё усложняет.

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

возможность поместить несколько .cpp в один модуль

Брось эту навязчивую идею.

grem ★★★★★
()
Последнее исправление: grem (всего исправлений: 1)
Ответ на: комментарий от fsb4000

Только вот если загуглить gcc module mapper и clang module mapper, то окажется, что они умели экспортировать информацию о модулях уже давно, просто с пометкой в документации «ой формат экспорта кажется нам некрасивым и мы его потом изменим». А CMake не торопился реализовывать его поддержку, хотя бы того что есть.

KivApple ★★★★★
() автор топика
Последнее исправление: KivApple (всего исправлений: 1)
Ответ на: комментарий от Lrrr

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

KivApple ★★★★★
() автор топика
Ответ на: комментарий от grem

Да я и не собирался нести ответственность за желающих оттяпать себе пальцы лопатой.

Тогда завязывайте с употреблением «нинужно».

Да, я понимаю разницу между include и import.

И почему этого незаметно?

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

В других языках есть шаблоны функций и классов, как в C++?

Если есть, то как там решили проблему экспорта шаблонов из модулей?

Шаблонов, не дженериков.

поэтому странно видеть желание реализовать подход, который не принёс никаких улучшений

Откуда дровишки про «не принес никаких улучшений»?

Большой опыт использования C++ных модулей мало у кого есть. Но рассказов о том, как все плохо пока не появилось.

Жалобы есть на то, что системы сборки не поддерживают модули. Так модули в самих компиляторах появились когда? А разрабы того же CMake просто криворукие ушлепки с дефективной ДНК.

Брось эту навязчивую идею.

«Если вы не будете указывать мне что делать, я не буду указывать куда вам пойти» (с)

Если мне проще (с точки зрения скорости разработки и безопасности по отношению к internal compiler error от компилятора) разбить модуль на пять cpp-ных файлов, то я рад тому, что такая возможность у меня есть. И мне как-то похер, что об этом думают на LOR-е.

eao197 ★★★★★
()
Ответ на: комментарий от KivApple

std::from_chars до сих пор для float работает только на MSVC. Почему? Потому что никто (в GCC и clang) не хочет писать реализацию с нуля, а использование strtof требует аллокации (чтобы приписать \0) запрещённой стандартом. То есть они 5 лет не могут написать конвертер строки во float.

В gcc 11 добавили реализацию, правда её нужно будет заменить со временем.

The implementation is a hack and not intended to be used in the long term. Rather than parsing the string directly, this determines the initial portion of the string that matches the pattern determined by the chars_format parameter, then creates a NTBS to be parsed by strtod (or strtold or strtof).

Because creating a NTBS requires allocating memory, but std::from_chars is noexcept, we need to be careful to minimise allocation. Even after being careful, allocation failure is still possible, and so a non-conforming std::no_more_memory error code might be returned.

Because strtod et al depend on the current locale, but std::from_chars does not, we change the current thread’s locale to «C» using newlocale and uselocale before calling strtod, and restore it afterwards.

https://gcc.gnu.org/pipermail/libstdc++/2020-July/050633.html

fsb4000 ★★★★★
()
Ответ на: комментарий от eao197

Мало ли кому что незаметно.

Хорошо, какие улучшения это принесло и почему мало у кого есть опыт работы с модулями, раз это так прекрасно реализовано?

Кроме cmake есть другие системы сборки. Подожди, в meson тоже только поддержка компилятора от ms. Куда ни глянь, все криворукие?

У тебя навязчивая идея разбивки одного модуля на кучу cpp файлов. С чего об этом речь пошла пока неясно, как и о, есть ли поддержка сего действия.

grem ★★★★★
()
Ответ на: комментарий от KivApple

В LLVM начиная с 14 есть to_chars: https://reviews.llvm.org/D70631

Осталось from_chars.

Правда потому что это заимстовано из MS STL, то там long double использует реализацию double (потому что в MSVC long double = double)

fsb4000 ★★★★★
()
Ответ на: комментарий от grem

Мало ли кому что незаметно.

Не мало. Между include <something-big> и import something-big разница принципиальная, т.к. в случае include компилятор вынужден каждый раз обрабатывать все потроха something-big и всего, что этот something-big подключает.

Т.е. если мы возьмем какую-нибудь crypto++, где практически все на шаблонах, то include <cryptopp/all.hpp> приведет к необходимости распарсить и перекомпилировать кучу всякого разного, в том числе и туеву хучу деталей реализации.

Тогда как в случае с import cryptopp ничего этого делать не нужно. Компилятор тупо возьмет уже сформированные представления и только для того, что выставлено из библиотеки наружу.

Так что import std по затратам будет сильно меньше, чем один include <iostream>.

Хорошо, какие улучшения это принесло

Говорят, что это напрочь устраняет проблему с нарушением ODR (one definition rule). Что уже окупает внедрение модулей.

Говорят так же (но здесь не точно), что скорость компиляции проектов с тяжелыми шаблонами увеличивается на 30%, а без шаблонов, так в разы.

Подожди, в meson тоже только поддержка компилятора от ms. Куда ни глянь, все криворукие?

А когда поддержка модулей появилась в других компиляторах?

Поддержка модулей заявлялась и в build2. Но тут такая штука, что build2 и даже meson – это экзотика на фоне CMake :(

У тебя навязчивая идея разбивки одного модуля на кучу cpp файлов. С чего об этом речь пошла пока неясно, как и о, есть ли поддержка сего действия.

Вас, наверное, сильно смущает, что пространство имен в C++ может быть одним и тем же сразу в нескольких .hpp/.cpp файлах. Или не смущает?

Если не смущает, то почему несколько .cpp-шников в одном модуле – это проблема?

Хотите примеров надобности иметь несколько .cpp-шников в одном модуле? Да сходу: один .cpp – для функциональности, общей для нескольких ОС, по одному .cpp для Win, Linux и macOS/FreeBSD. Скажем, для реализации IPC через shared-memory или использования функциональности sendfile.

Модули в C++ лучше рассматривать как пакеты в других языках.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

А разрабы того же CMake просто криворукие ушлепки с дефективной ДНК.

Meson тоже не поддерживает модули, кроме MSVC. И кто вообще поддерживает?

KivApple ★★★★★
() автор топика
Ответ на: комментарий от fsb4000

Устраивать аллокацию памяти и менять локали для простого парсинга float это очень большое извращение, которое знатно уронит производительность. Что мешало скопировать реализацию из strtof (которая является частью кодовой базы компилятора и должна быть лицензионно совместима) просто заменив условие выхода s == '\0' на s + i == end?

KivApple ★★★★★
() автор топика
Ответ на: комментарий от eao197

Не мало. Между…

Это всё секрет Полишинеля.

Дробление на более мелкие модули полезно с той точки зрения, что не нужно его весь пересобирать при изменении реализации, нужно пересобиать только мелкий. Не говоря о том,что подключая модуль, включающий «всё» тащится куча ненужных никому в данном проекте пространств имён, интерфейсов и всё что в него понапихали. Сомневаюсь, что это что-то ускорит во время сборки и линковки.

Мне не нужен пример, я спрашивал о том, реализована ли линковка нескольких объектных файлов в один модуль?

grem ★★★★★
()
Ответ на: комментарий от grem

Сомневаюсь, что это что-то ускорит во время сборки и линковки.

Проверьте сами: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2412r0.pdf Тут некоторые циферьки есть.

Мне не нужен пример, я спрашивал о том, реализована ли линковка нескольких объектных файлов в один модуль?

Я вам выше пример привел из документации к clang-у. Точнее чем там я вам не опишу, сорри.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Сейчас буду ворчать, что тесты не очень. Ну то есть они демонстрируют преимущество модулей перед хедерами, но почему-то нет сравнения с precompiled headers, раз уж дело с std имеют.

Просто обидно, что фича долгожданная многими, но толком её начать использовать пока нельзя из-за отсутствия поддержки в системах сборки. Проекты поменьше на это забить могут и явно указывать зависимости, пусть даже сгенерив их списки самописными скриптами, например, на awk.

Я превратился в старого брюзгу, только озабоченного фортраном, а не растом :)

grem ★★★★★
()
Ответ на: комментарий от fsb4000

К сожалению, на этом сайте ничего невозможно рассмотреть со смартфона, так как для них он сильно сжимает картинки.

grem ★★★★★
()
Ответ на: комментарий от grem

У gcc плохая ситуация с precompiled headers - он любит крашиться при их сочетании с heap address randomization, которая включена почти везде на 64 битах. Кажется, вместо нормального формата сериализации они сделали дамп памяти.

KivApple ★★★★★
() автор топика
Ответ на: комментарий от KivApple

Кажется, вместо нормального формата сериализации они сделали дамп памяти.

Precompiled headers это и есть дамп памяти компилятора.

В MSVC и clang это также.

А в модулях нормальный формат сериализации.

fsb4000 ★★★★★
()
Ответ на: комментарий от fsb4000

Стандарт не заставляет делать pch через дамп памяти. Ничто не мешало сделать нормально. И даже если дамп памяти, то хотя бы чтобы был совместим с рандомизацией кучи.

KivApple ★★★★★
() автор топика
Ответ на: комментарий от KivApple

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

grem ★★★★★
()
Последнее исправление: grem (всего исправлений: 1)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.