What to do with C++ modules? 💬 Длинная дискуссия
Краткий обзор проблемы C++ модулей
-
Главное требование
Если модули не ускоряют сборку минимум в 5 раз (желательно 10×) на реальных проектах, их нужно удалить из стандарта. Все остальные плюсы не стоят вложенных ресурсов. -
Что обещали vs. что получили
- Изначально: «уберём O(N²) из-за заголовков, компиляция станет мгновенной».
- Сейчас: упор сместился на «изоляцию сборки» (макросы, пространства имён). Это полезно, но редко встречается и не решает главную боль — медленную сборку каждый день.
-
Почему всё так плохо
- Модули приняли в C++20, несмотря на предупреждения о невозможности реализации.
- Реализация заняла >5 лет и всё ещё не готова.
- Стандарт не описывает, как именно компилятор и сборочная система должны взаимодействовать: имена файлов, каталоги, зависимости — всё на совести разработчиков.
- Компиляторные команды отказываются «превращаться в систему сборки» и блокируют любые предложения.
-
Итог
Проект превратился в «интеграционный ад». Пока нет массовых 5-10-кратных ускорений, дальнейшие инвестиции — просто затягивание «затратной ямы».
Комментарии (165)
- Участники сетуют: вместо простого «import = include без утечек контекста» получили громоздкий механизм, который мало кто использует.
- Старые пре-компил-хедеры 90-х и сторонние решения вроде zapcc уже давали 5× ускорение, но были проигнорированы стандартом.
- Модули обещали избавить от forward-declaration и макро-ifdef, но на практике вызывают лавину пересборок и несовместимы с большим объёмом существующего кода.
- Многие считают, что модули заточены под «большой тех» с кэшированными билдами, а малый бизнес и хобби-проекты «попали в пролёт».
- Итоговое настроение: «убейте модули, C++ всё сломали», «мир ушёл в Rust», но «на C++ всё ещё держится пол-мира, так что просто так не выкинешь».
C++: Strongly Happens Before?
Коротко о «strongly happens-before»
В C++20 появилось новое отношение strongly happens-before (SHB), потому что старое happens-before оказалось недостаточно строгим.
Пример кода
std::atomic<int> x{0}, y{0};
// thread-1
x.store(1, seq_cst);
y.store(1, release);
// thread-2
int b = y.fetch_add(1, seq_cst); // b = 1
int c = y.load(relaxed); // c = 3
// thread-3
y.store(3, seq_cst);
int a = x.load(seq_cst); // a = 0
Почему нужен SHB
- Каждый атомарный объект имеет modification order — полный порядок всех записей.
- Классические правила coherence (write-write, read-read, read-write, write-read) связывают happens-before с этим порядком.
- Но при смешанных
memory_order(например,release+seq_cst) между потоками могут образоваться «дыры», где старое happens-before не гарантирует ожидаемую последовательность.
Отличие SHB от HB
- SHB добавляет требование: если операция A strongly happens-before B, то все наблюдатели видят записи A раньше записей B, даже при разных
memory_order. - Для
seq_cstSHB совпадает с HB; дляrelease/acquireHB может быть, а SHB — нет.
Итог
Если пользуетесь только seq_cst, можно не заморачиваться. При смешанных порядках SHB — формальный способ не получить «невозможные» значения.
Комментарии (14)
- Участники обсуждают различие между «strong happens-before» в Java и более слабой моделью C++.
- Код из статьи считается не «простым», а академическим примером; порядок запуска потоков ОС не гарантирует.
- Наблюдаемые в комментариях значения могут показаться странными, но они допустимы при отсутствии дополнительной синхронизации.
- Смысл слабых моделей памяти — формально описать множество всех допустимых исполнений, учитывая кэши, оптимизации компилятора и т. д.
- Добавление ещё одного атомика для «упорядочивания» может, наоборот, запретить именно тот «неинтуитивный» порядок, который автор статьи исследует.
Compiler Bug Causes Compiler Bug: How a 12-Year-Old G++ Bug Took Down Solidity
Краткий обзор
- Проблема: компилятор Solidity (solc) падает на Ubuntu 22.04 при компиляции корректного кода.
- Причина: сочетание трёх факторов
- 12-летний баг G++ (< 14) в разрешении перегрузок.
- Устаревший паттерн сравнения в Boost.
- Новые правила симметричных сравнений C++20.
Цепочка событий
- Баг G++ (2012, GCC-53499): при
boost::rational<T> == 0компилятор до 14-й версии выбирает нечлен-шаблон вместо член-шаблона. - C++20 добавляет автоматическую перестановку аргументов:
0 == rational<T>→rational<T> == 0. - Boost 1.74 предоставляет обе версии оператора, что приводит к бесконечной рекурсии и переполнению стека.
Минимальный пример
template<typename T>
struct rational {
template<class U>
bool operator==(const U&) const { return true; }
};
template<class U, class T>
bool operator==(const rational<T>&, const U&) { return false; }
int main() {
rational<int> r;
return r == 0; // g++11 выбирает free-функцию
}
Как починить
- Обновить GCC ≥ 14 или Clang, или
- Собрать Solidity без C++20 (
-std=c++17), или - Патч Boost/использовать свежий Boost ≥ 1.82.
Итог
Ни один компонент по отдельности не «сломан», но их комбинация приводит к крашу компилятора на валидном коде.
Комментарии (74)
- 12-летний баг разрешения перегрузок в GCC + новый оператор <=> C++20 = краш компилятора Solidity.
- Проблема в том, что «каждый компонент по отдельности не сломан», но вместе дают сбой.
- Участники обвиняют чрезмерную сложность C++, отсутствие тестов при обновлении стандарта и «бесконечные надстройки» вроде spaceship-оператора.
- Кто-то предлагает «python2to3-момент» для C++, другие считают смарт-контракты плохой идеей из-за неизбежных багов.
- Лицензия SPDX в примере вызывает вопросы, но Solidity требует её наличие, иначе ошибка компиляции.