Как стать автором
Обновить

Комментарии 17

Долговечная constexpr-память

тут я не понял последний пример - почему ui.reset() для const unique_ptr& OK? Это ведь иммутабельный объект. Плюс к тому, я не совсем понял как propconst должен решить проблему constexpr p = make_unique<int>(); В идеале должна выделиться compile-time память под мутабельный в рантайме int. А записьconstexpr auto p = make_unique<unique_ptr<int>>(make_unique<int>()); [1] - выделять compile-time память под указатель на (внимание!) compile-time память под мутабельный int. То есть в этом случае константность должна пропагандироваться не просто так, а неким нетривиальным образом, учитывающим/перепроверяющим то, будет ли аллоцировавший в compile-time объект перетекать в рантайм. То есть компилятор должен заставить нас преобразовать [1] в constexpr auto p = make_unique<const unique_ptr<int>>(make_unique<int>());

Стандарт дает уклончивый ответ на тему того, когда запись в результат const_cast-а может спровоцировать Undefined Behaviour

не шибко то и уклончиво - запись в const объект всегда ведет к UB. А const_cast лишь преобразует типы ссылок/указателей.

тут я не понял последний пример - почему ui.reset() для const unique_ptr& OK? Это ведь иммутабельный объект.

Я закомментировал строку с ui.reset(), имея в виду, что эта строка не скомпилируется, но сейчас вижу, что текущий вид сбивает с толку. Спасибо, поменяю!

Плюс к тому, я не совсем понял как propconst должен решить проблему constexpr p = make_unique<int>(); В идеале должна выделиться compile-time память под мутабельный в рантайме int.

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

По задумке авторов предложения, non-transient память должна быть разрешена, только если никаким образом нельзя получить к какой-то ее части mutable-доступ (без const_cast и пр. хаков). Ясно что объект const unique_ptr<int> недостаточно силён, чтобы это гарантировать.

Смысл попытки введения propconst в том, чтобы усилить выразительность языка, и гарантировать иммутабельность всего, что нагенерировалось в constexpr. Объект типа const unique_ptr<int propconst> смог бы это гарантировать.

Тем не менее, предложение еще в разработке, и финальный результат (non-transient память) мы увидим в С++26 или дальше, потому что feature freeze для C++23 происходит в 2021 году.

не шибко то и уклончиво - запись в const объект всегда ведет к UB. А const_cast лишь преобразует типы ссылок/указателей.

Перечитал еще раз стандарт - да, так и есть, const_cast "can produce UB" лишь постольку, что может являться "посредником" для записи в const-объект. Неоднозначностей вроде нет.

Спасибо за статью. Жаль что тернистый путь constexpr еще далек от завершения. DR253 не упомянут по упущению?

Спасибо! Пока не самая простая часть языка :-)

Очень интересно, хочется пример зачем это может быть нужным — в смысле зачем программе выполняться на этапе компиляции.

Например, в микроконтроллерах STM есть Bit Banding (атомарная модификация отдельных битов), который полезен для модификации некоторых регистров, но он поддерживается не во всем адресном пространстве, т.е. по хорошему нужно делать валидацию чтобы не дебажить потом часами устройство из-за одной опечатки.
Но валидация во время выполнения впустую тратит процессорное время т.к. в большинстве случаев нужный адрес известен уже на этапе компиляции, а самое главное, валидация занимает место во flash памяти, которой зачастую не так много как хотелось бы.
В данном случае constexpr подходит просто идеально, позволяя абсолютно бесплатно выполнить вычисление нужного адреса и его валидацию еще на этапе компиляции.
PS: На мой взгляд constexpr - фича крайне полезная, в моем случае она позволила убрать кучу проверок и вычислений из рантайма и уменьшить объем прошивки примерно на 20%.

Пример кода
template<uint32 address, uint32 bitMask>
constexpr uint32 GET_BIT_BAND_ADDR()
{
	// Области памяти поддерживающие bit-banding
	constexpr uint32 ONE_MEGABYTE = 0xFFFFC;
	constexpr mem_region sram_area = mem_region(SRAM_BASE, SRAM_BASE + ONE_MEGABYTE);
	constexpr mem_region periph_area = mem_region(PERIPH_BASE, PERIPH_BASE + ONE_MEGABYTE);
	static_assert(sram_area.contains<address>() || periph_area.contains<address>(), "Invalid address");
	
	// Проверяем что установлен только один бит
	static_assert(IsPowOf2<bitMask>(), "Invalid bitMask");

	constexpr auto bitNumber = LSB_Number<bitMask>();
	uint32 bitAddress = 0;
	if (sram_area.contains<address>())
	{
		constexpr auto bitBandBaseAddress = SRAM_BASE + 0x2000000;
		bitAddress = bitBandBaseAddress + (address - SRAM_BASE) * sizeof(uint32) * 8 + bitNumber * sizeof(uint32);
	}
	if (periph_area.contains<address>())
	{
		constexpr auto bitBandBaseAddress = PERIPH_BASE + 0x2000000;
		bitAddress = bitBandBaseAddress + (address - PERIPH_BASE) * sizeof(uint32) * 8 + bitNumber * sizeof(uint32);
	}
	return bitAddress;
}
Ага, спасибо! Мы иногда МК тоже программируем — может оказаться полезным.

Последним if лучше сделать else if,

а еще лучше Статик ассерт какой нить что адрес одновременно не попадает в периферию и срам. Так как sram_area и periph_area объекты классов, которым че то из вне заносят. В макросах типа SRAM_BASE могут быть и ошибки, непонятно же откуда он пришел.

Эти макросы берутся из хидера от STMicroelectronics, конечно это не исключает возможных ошибок, но шансы столкнуться с ошибками в них довольно малы т.к. эти контроллеры очень широко используют. Разве что какой-нибудь диверсант решит переопределить макрос, думаю что заморочки такого плана могут иметь смысл только в устройствах от которых напрямую зависит безопасность людей.
А насчет else if, понятно что тут лишняя проверка в одном из вариантов, но ведь это выражение все равно будет вычислено на этапе компиляции. Честно говоря else if мне не понравился тем что портит выравнивание, увеличивает объем кода и при этом никак не влияет на прошивку, которая окажется в контроллере, так что я оставил просто if :-)

Это перевод инициализации во время работы программы в инициализацию на этапе компиляции. Пример constexpr, который может находиться почти в каждом .cpp файле:

constexpr std::string_view HELLO_WORLD = "Hello world";

Если бы вместо constexpr был const, то этот объект построился бы в старте работы программы до вызова int main(). В больших программах таких объектов много, до нескольких тысяч.

Также в compile-time строить прочие простые объекты: контейнеры (пока не STL), регулярные выражения.

Для микронтроллеров это вообще актуально: объявил кучу констант, проинициализировал их. Не факт, что они лягут во Flash, может положить и в ОЗУ, таким образом, он при старте, будет копировать инициальные значения из памяти программ в ОЗУ, где лежит константа. И если таких констант много, куча лишнего кода + время перекидывания. А если все константы объявить сразу constexp, то ничего этого нет.

Компиляторы для микроконтроллеров в основном конечно пытаются простые константы так не делать, но константные объекты структур и классов уже в ОЗУ ложат, многие.

Кстати и обычные STL контейнеры довольно просто можно сделать constexpr. Я занимался подобным патчингом libc++, когда готовил предложения по constexprфикации STL-ных контейнеров. Из минусов хочу отметить, что там по дороге нужно разметить как constexpr большую часть библиотеки. И ещё компилятор на тот момент (Clang trunk) очень часто уходил в ICE.

НЛО прилетело и опубликовало эту надпись здесь
О! За производные прям спасибо огромнейшее! Мне реально это нужно!
Есть своя программа которая считает магнитное поле от произвольного линейного в пространстве и там как раз нужно чтобы производные считались «сами».

Мне кажется, после C++14 уже перебор вышел с constexpr. Вызывать какие-то функции это ещё нормально, но вот выделять/освобождать память - это перебор.

Ещё не понял, зачем нужен constexpr!для функций, если можно использовать constexpr для результата её вызова.

На самом деле нужно не само выделение памяти, а возможность создавать различные контейнеры, которые его используют в своей реализации (std::vector, std::unordered_map и т.д.). Например, для поддержки рефлексии в будущем.

Господа...


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


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


Подробнее можно познакомится с темой тут:
https://0x1.tv/Constexpr_%E2%80%94_%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%BE%D0%B5_%D0%B1%D0%BB%D0%B0%D0%B3%D0%BE,_%D0%B2%D1%8B%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%BD%D0%BE%D0%B5_%D0%B2_%D0%BD%D0%B5%D0%BF%D1%80%D0%B0%D0%B2%D0%B8%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9_%D0%B8%D0%B4%D0%B5%D0%B5_(%D0%95%D0%B2%D0%B3%D0%B5%D0%BD%D0%B8%D0%B9_%D0%9A%D0%BB%D0%B8%D0%BC%D0%B5%D0%BD%D0%BA%D0%BE%D0%B2,_ISPRASOPEN-2019)
https://ieeexplore.ieee.org/document/8991150 (sci-hub.st вам в помощь)

так же вскоре появится статья по этой же теме с конференции IEEE CSIT 2021.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации