Comments 54
А что там по Safe C++, были разговоры? Или все, мертв?
Safe C++ в своих примерах использует `#feature`, который является аналогом профилей. Так что комитет прежде всего сконцентрируется на профилях, при этом borrow checking (P3390 Safe C++) будет развиваться параллельно.
Есть небольшой шанс увидеть Safe C++ в C++29, но работа предстоит титаническая
Тут пишут, что профили - это мертворожденный продукт, который будет допилен до конца приблизительно никогда https://izzys.casa/2024/11/on-safe-cxx/
template <class T>
class Pencil trivially_relocatable_if_eligible {
// ...
};Честно, вызывает смущение синтаксис trivially_relocatable_if_eligible, ИМХО лучше бы это был атрибут =) Тем более такой длинный. А если потом захочется еще какое-нибудь знание протащить в класс (мета-информацию), дальше в строчку пихать или как. clang-format наверное одуреет с этого =). С учетом что щас нет пользовательских атрибутов (кроме разных самописных плагинов к компилятору), почему бы и нет, никого не обидит резервированный атрибут
Ну и очередная штука которую по идее хочется пихать почти везде, как всякие noexcept, const, explicit, компилятор сам не будет принимать решения о trivially_relocatable? Даже для какого-нибудь Point ?
struct Point {
double x = 0;
double y = 0;
}С атрибутом будет длиннее на 4 символа т.к. `[[]]` :)
Все старые правила по оптимизациям тривиальных типов в компиляторе остаются. Если нет особой нужны различать trivially_relocatable|nothrow movable|trivial, то в стандартную библиотеку в том же P2786 добавили std::relocate(from_begin, from_end, to), который для Point так же превратится в std::memmove.
Спасибо, окей, по кишкам нет вопросов
Возвращаюсь в область вкусовщины и синтаксиса :)
Для этой фичи, которая кмк весьма опциональная, ввели аж ключевое слово. Я бы понял, если например механизмы шаред\юник птры обернули бы в ключевые слова и языковые конструкции (не библиотечные), чтобы где-то добавить им элегантности - это повсеместная история.
Новое ключевое слово - еще больше усложняется разбор исходного кода C++, нужно поддержать всякие matcher-ы, разрабам компиляторов сильно больше работы (чем в сравнении с п.3)
Это при том что в синтаксисе уже есть работающий механизм толкать мета-информацию к символам - атрибуты. Зачем вводить вводить новый механизм, если можно использовать старый
С атрибутом будет длиннее на 4 символа т.к. [[]]
4.Я имел ввиду что это могло бы выглядеть примерно так, как подобные вещи решаются в Java\C#\Rust\... почти любой современный язык на самом деле, про динамические и говорить не нужно:
template <class T>
[[trivially_relocatable_if_eligible]]
class Pencil {
// ...
};Давайте попробуем масштабировать количество атрибутов, допустим еще +2 опциональной мета-информации с большим именем появится в С++29 или еще где, неважно. Получается на любую такую фичу нужно вводить новое ключевое слово? Или только выборочно, значит какие-то вещи через атрибуты, какие-то нет
// OK
template <class T>
[[trivially_relocatable_if_eligible]]
[[serializable_if_trivial]]
[[hello_from_stdcpp_mazafaka]]
class Pencil {
// ...
};
// Сомнительно
template <class T>
class Pencil trivially_relocatable_if_eligible serializable_if_trivial hello_from_stdcpp_mazafaka {
// ...
};
// Сомнительно, но окей (вкусовщина, но считаю хуже первого варианта)
// + завал мусорных ключевых слов,
// которые не должны быть ключевыми словами (имхо)
template <class T>
class Pencil
trivially_relocatable_if_eligible
serializable_if_trivial
hello_from_stdcpp_mazafaka {
// ...
};Толкать метаинформацию, влияющую на ABI через атрибуты на практике получается неприятно. Комитету не понравилось как получилось с [[no_unique_address]], поэтому больше так делать не хотят. Контекстно зависимое ключевое слово в этом плане лучше - точно применится или будет ошибка компиляции, проще диагностировать неправильные использования.
Ну а что касается вкусовщины - излишнее количество [[]] не идёт языку на пользу. Читается хуже и язык превращается из C++ в C[[]] %)
Агап, про [[]] пишу "вынуждено", маловероятно что будут менять синтаксис атрибутов.
Но если бы меняли, лично мне нравится Java вариант, достаточно элегантно
@trivially_relocatable_if_eligible
class Pencil {}Спасибо, чуть понятнее стало почему так (хоть и все равно не очень нравится концептуально по причинам из пунктов)
Подобный вариант хотят сделать для пользовательских атрибутов, которые будут использоваться рефлексией. Но там пока даже особо не обсуждалась идея - сначала нужна основная часть рефлексии
#embed — ещё одна долгожданная новинка для C++26
Почему как директива препроцессора, у C++ нет "курса" на отход от этого? Только чтобы с языком C было одинаково? Как будто constexpr (consteval) функция в данном случае было бы идеоматически правильнее.
На этапе компиляции ведь файл там же где и был во время препроцессинга, если про .cpp речь, ну с хедерами не сканает так окей. Можно основной интерфейс сделать constexpr функцией c параметрами, макросом только нужную от препроцессора штуку передавать - относительный путь к файлу, в смысле разрулить абсолютный путь, чтобы у единицы трансляции был к нему доступ.
Потому что добавить этот функционал в С++ автору предложения изначально не дали, он его отбэкпортил в Си, комитет по Си его принял, и теперь он именно в сишном виде таки доезжает до С++.
Edit: полезная ссылка https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p1040r7.html
На этапе компиляции ведь файл там же где и был во время препроцессинга
Кажется что тогда сломается кеширование объектников по хешу препроцессирования - изменение ресурса не будет приводить к пересборке. А значит сломается ccache и куча аналогичных enterprise решений
а почему вклчюение заголовчных файлов и файлов ресурсов должно семантически отличаться? я наоборт за #embed ну и включать ресурсы хочется не только в cpp но и в c тоже
А как соотносится Relocate с destructive move? Можно его в роли такового рассматривать? Или обращение к Relocate-нутому объекту будет старым добрым UB?
поидее суть в том что после релокации, гдето перед циклом, возможно, может быть вызов итератора на контейнер, и он будет уже текущим, а вот прям принципиально по вашему вопросу интересно,
Скрытый текст
...
relocate(from_begin, from_end, to);
...
::iterator<NameContainer> it = valueName.begin();
...
cyclelifetime relocate-нутого объекта завершён. Его больше нет, так что обращение будет старым добрым UB.
Ничего не понятно про этот релокэйт, но очень интересно.
auto x = std::make_unique<int>();
auto y = std::unique_ptr<int>();
std::relocate(&x, &x +1, &y);Вопрос, это вообще ок или это ещё один способ написать УБ? Если ок, то будет ли вызван деструктор для х? А что насчёт у был не пустым?
Не нравится ни trivial relocation через новое ключевое слово, ни контракты. Как мне написать структуру, которая для тривиального T тривиальная, а для нетривиального - нет? Да никак. Потому что это не специализация внешней std::trivial_relocatable<T>, а ключевое слово. И опшнлы в стандартной библиотеке никогда не получат этого нововведения получается и т.д.
Насчёт контрактов, синтаксис - сомнительный, изменение поведения - ещё сомнительнее. Нет по дефолту поведения UB на контракте. Все будут ставить observe и писать unreachable.
Более того, я уже вижу как их будут использовать:
#define LOGME pre(false)
void handle_contract_violation() {
print(std::stacktrace::current().front().name());
}
void foo(int i) LOGME {
...
}
Как мне написать структуру, которая для тривиального T тривиальная, а для нетривиального - нет
В имени аргумента if_eligible не просто так. Компилятор проверит что все члены класса тоже trivially_relocatable, и только тогда трейт будет возвращать что класс действительно тривиально релоцируемый. Например:
template <class T>
class optional trivially_relocatable_if_eligible {
union {
Empty empty{};
T payload;
} data_;
// ...
};Приведённый optional будет trivially_relocatable если ABI платформы разрешают делать trivially_relocatable юнионы и T является trivially_relocatable.
Нет по дефолту поведения UB на контракте
Этот режим "вынули" из C++26. Дальше в планах его добавить, чтобы компилятор мог оптимизировать из расчёта на то, что контракт никогда не нарушается.
Более того, я уже вижу как их будут использовать:
Ну да, можно многими конструкциям во многих языках программирования пользоваться заведомо неправильно. В случае контрактов это будет как минимум не удобно, ведь проще сделать:
#define LOGME std::print(std::stacktrace::current().front().name())
void foo(int i) {
LOGME;
...
}Очень расстраивает, что быстро принимают сомнительные предложения, вроде trivially relocatable. При этом реально нужные вещи вроде рефлексии проходят очень медленно. А некоторые нужные предложения, вроде embed, вообще пришлось заносить через C23.
Идее relocate больше 20 лет :) Во многих библиотеках подобный подход использовался и кастомизировался с помощью специфичных для библиотеки трейтов. Предложение по relocate просто стандартизирует способ подобного указания.
Рефлексия движется крайне быстро. Я вообще удивлён что так молниеносно удалось 4 различные популярные реализации C++ фронтенда свести к общей части - рефлексии.
Бедный assume[[expression]] (since C++23)... Контракты полностью его функционал покрывают (а он покрывает компайл-тайм фичи контрактов).
Вопрос - а у нас есть возможность в C++26 мувнуть куда-то локальный std::unique_ptr таким образом, чтобы компилятор понял, что вставлять вызов деструктора уже не надо? Ну или какой-нибудь другой класс, который либо владеет ресурсом, либо не владеет, скажем, std::any?
Блок про relocate как раз об этом, по идее.
И что, он вызовет мув-оператор копирования / мув-конструктор? Я именно про мув говорю.
Сам по себе мув же ничего не делает. Поведение будет зависеть от того, как именно его реализовать в классе - можно переприсвоить ресурсоы, а можно, как я понял, попробовать сделать релокацию. И тогда:
После этого можно использовать std::trivially_relocate(from_begin, from_end, to) функцию, чтобы переместить объект, завершить время жизни (lifetime) старых объектов и начать время жизни новых объектов. На практике, функция будет перемещать объекты через std::memmove, полностью избегая вызовов конструкторов и деструкторов.
Так, ничего не понимаю. Если релокацию планируют писать внутри мувающего оператора присваивания, то откуда компилятор должен вытащить информацию, как реализован этот мув? А ему придётся это узнать, чтобы решить - вставлять ли на выходе из скоупа деструктор локальной переменной или нет. А вдруг std::relocate вызывается только в какой-то ветке, что тогда делать компилятору?
Да, пока что relocate будет использоваться только в контейнерах. При передаче параметров в функции ABI не позволяет воспользоваться relocate для стандартных типов. И нет способа сделать relocate для локальной переменной, чтобы компилятор для себя отметил, что деструктор тут не вызывать.
Можно было бы начать с того, чтобы добавить в контейнеры функции, которые вы-relocate-ивают элемент наружу:
optional::pluck() -> Tvector::pluck_back() -> T
И функции, которые вставляют элемент внутрь без move:
optional::emplace_func(std::invocable_r<T> auto)vector::emplace_back_func(std::invocable_r<T> auto)
Тогда, используя optional, можно иметь подобие move-семантики Rust, только флаг живости объекта будет присутствовать в коде, а не трекаться автоматически.
В стандартную библиотеку это не пролезет, пока не будет принято решение сломать ABI. А это будет ещё не скоро (а может и не будет, хотя по слухам MS всё-таки что-то замышляет).
Наконец-то первый нетривиальный способ выстрелить другому в ногу. Это уже дуэль.
Нормальным перемещение в C++ будет только тогда, когда компилятор наконец то будет иметь возможность по крайней мере для локальных переменных и аргументов функций вызвать такое перемещение, которое не будет подразумевать последующего вызова деструктора для перемещаемой переменной. При этом должна быть ещё и проверка, что не происходит условного перемещения и нету обращения к перемещённым переменным.
Что же касается текущего std::move - то его можно оставить для случаев, когда перемещается какой-то элемент структуры/массива или же элемент контейнера.
Я тоже горю идеей destructive move, но со временем она кажется всё менее и менее привлекательной. Современные компиляторы очень неплохо убирают мертвый код деструкторов вида `~Foo() { if (not_moved) delete not_moved; }`. Современный LTO тоже весьма неплохо помогает компилятору в более сложных случаях move+destroy
Если идею доработают до разумного состояния (последние предложения ломали приложения на ряде платформ и очень усложняли язык) - будет маленький праздник.
А если мой класс имеет какое-то уникальное состояние, сохраняющееся после мува, и влияющее на результат работы деструктора? Я вполне себе уверен, что такое свойство в общем случае нетривиально, а значит алгоритмически неразрешимо. А удалять код вида if (has_value) delete ..., как уже отметили, они и сейчас могут
Случай с embed показателен в плане медлительности и неэффективности комитета стандартизации. Фича по сути своей почти что тривиальная, реализуется с перерывами на кофе и митинги за неделю в любом компиляторе C++. Но почему-то стандартизация тянется на годы, если не на десятилетия. Почему так?
При этом есть ряд фич, которые после принятия показали себя как нежизнеспособные. Например std::regex по сути бесполезен, т. к. стандарт не гарантирует, что он поддерживает UTF-8. Ну и ещё он жутко медленный. При этом его можно было бы грамотно реализовать, если поручить компиляцию регулярных выражений, известных во времени компиляции, самому компилятору C++ - чтобы они напрямую в машинный код трансформировались. До до этого комитет почему-то не дошёл.
Случай с embed показателен в плане медлительности и неэффективности комитета стандартизации.
А на мой взгляд это успех - забраковали первые версии embed, которые ломали сборку и разработку множеству компаний; подсказали что правильнее реализовывать функционал через препроцессор и для лучшей совместимости лучше реализовать для C стандарта; полученный из комитета C #embed отревьюили опять, поправили проблемы, приняли в C++ и отправили в C изменения для синхронизации.
Фича по сути своей почти что тривиальная, реализуется с перерывами на кофе и митинги за неделю в любом компиляторе C++.
Количество комитов для поддержки embed в GCC перевалило уже за второй десяток, разработка и доработка идёт больше полугода. Кажется вы очень сильно недооцениваете проблему
При этом его можно было бы грамотно реализовать, если поручить компиляцию регулярных выражений, известных во времени компиляции, самому компилятору C++ - чтобы они напрямую в машинный код трансформировались. До до этого комитет почему-то не дошёл.
Вот только автор этого подхода и библиотеки ctre - как раз часть комитета C++ и она как раз втащила огромное количество constexpr улучшений, необходимых в дальнейшем как для рефлексии, так и для стандартизации ctre.
С "за одну неделю" я может и погорячился, но всё же embed не кажется чем-то, что уж настолько сложно, в сравнении с другими нововведениями.
Про забраковали - можно поподробнее? Что там не так было? И не лучше ли было бы принять в стандарт не самую идеальную, но работающую версию, чтобы пользователи языка не были вынуждены несколько лет ждать?
Да фиг бы с ними - с компайл-тайм регулярками... std::regex тупо небезопасен - мэтчинг по конечному автомату реализован рекурсивно, а не итеративно, поэтому на некоторых входах оно просто сегфолтит из-за stackoverflow, роняя всё приложение. Этот баг был замечен в gcc ещё в 2010х годах, но его не починили, как я понял, из-за ABI. Итеративный regex есть в boost, но... Процитирую автора ImHex:
Oh yeah, boost regex depends on:
assert
concept_check
config
container_hash
core
integer
mpl
predef
smart_ptr
static_assert
throw_exception
type_traits
Нет, бывает и больше зависимостей...
Впрочем, какие-то энтузиасты смогли переписать бустовый regex, чтобы он полагался только на стандартную либу, так что мы перешли на эту standalone версию. Вариант "не пихать туда непроверенный ввод" не годится, т.к. ImHex делает поиск в файле, который открывает пользователь.
С Boost.Regex остаётся проблема, что он позволяет backtracking, а соответсвенно уязвим к ReDOS атакам :(
Рекомендую использовать Re2
P.S.: у меня в команде хотели написать статью на эту тему. Было бы вам интересно почитать?
Во-первых, да, было бы!
Во-вторых, не смог сходу найти статей про конкретно Boost.Regex от John Maddock, в которых бы демонстрировался ReDOS. Одну обзорную статью нашёл, в которой было много движков разобрано, там приводился конкретный пример, но я не смог воспроизвести атаку: ImHex отработал очень быстро.

По-моему, если пользователь введёт дурной паттерн, то он имеет шанс самостоятельно закопаться, уж такова природа работы с неизвестными данными. Он с тем же успехом может не рассчитать, хватит ли у него оперативы для какой-нибудь другой тяжёлой операции, и прогореть. Если ReDOS страшнее, чем это, надо будет подумать.
В-третьих, Re2 тащит за собой Abseil (кучу либ, дополнующий стандартную либу плюсов), т.е. наступает на грабли самого Буста.
Что за штука, не подскажете ?
скрестили пальцы за рефлексию, все остальное это баловство...
Очень интересно было бы послушать про placement new, launder и лайфтаймы с точки зрения стандарта.
Казалось бы простая вещь: вызвать конструктор по адресу, но повсюду ждёт UB.
ЯП улучшаются, а качество ПО ухудшается. Хотелось бы услышать мнения гуру: зачем нужны новшества, если конечный результат их использования будет заведомо плохим?
Не могу сказать за другие языки программирования, но качество C++ ПО ощутимо улучшается. Появились санитайзеры, выработалась культура по обязательному написанию тестов и использованию CI, компиляторы обзавелись внушительным количеством встроенной диагностики, меньше людей пишет в стиле "C++ с классами" (в институтах начали нормально рассказывать про RAII!), всё меньше проектов с самописными и забагованными версиями variant|string|optional. Да даже если судить по одним только Хабру и чатику pro.cxx, уровень квалификации очень ощутимо вырос за последние 15 лет.
У меня другие наблюдения. С одной стороны простой код действительно стал безопасней из коробки и поэтому простым девелоперам сосредоточенным на бизнес логике стало легче писать на плюсах. С другой стороны есть такая категория девелоперов которые явно умнее среднего, но не достаточно, они то как раз герои пословицы горе от ума. Поясню, они достаточно умны, чтобы написать нетривиальный системный/библиотечный код который полон УБ, но работает почти всегда правильно. Ума написать такой же, но без УБ уже не хватает. Это часто проблемы в многопоточном программирование, безопасности памяти, исключений.
Такие люди создают код который приносит невероятного масштаба жопу. Починить(с учётом уже всех существующих юз кейсов) очень сложно, переписать на стандартные/надёжные решения очень дорого, т.к. они отличаются архитектурно/по ограничениям.
Так вот последние нововведения в плюсы вообще никак не помогает таким людям писать более безопасным образом. Атомики прекрасный новый неисчерпаемый кладезь УБ. Понять модель памяти с++ не реально, можно только простить. Написать тесты на код с атомиками задача невероятной сложности. Да даже многопоточные тесты это 99,999% это тупо таймеры. Ни одна душенка в комитете даже не задумывается на эту тему - мы вам дали автомат, а технику безопасности сами как нибудь. Шаред поинтеры, до массового использования атомиков были проблемой номер 1, но атомики конечно победили с большим отрывом. Прекрасный дизайн шаред принтеров спустили в унитаз под названием ГЦ для бедных, только они не ГЦ и проблем создали намного больше чем решили. Теперь понять, что когда умирает в продуктовом коде решительно невозможно. Казалось бы в теории я и не должен, но в реальности ещё как должен.
Новое модное течение - рэнджи. Опять же казалось бы такая классная штука, но теперь рэнджи ради рэнджей везде и понять стало сложнее и тормозить стало больше. Ну спасибо чё.
То что делает комитет все нужно и действительно важно, но вы там почему то забываете писать много хороших примеров как пользоваться новыми игрушками, но ещё важнее написать как НЕ надо пользоваться ими. Где вашу мать техника безопасности на новые приблуды?
#embed )))
C++26 — встреча ISO в Хагенберге