А теперь раскомментируем строки №№ 10 и 14 … опс … перестала собираться
В чём тут проблема?
Дело в том, что проинициализировать одно поле union, а использовать другое – это т.н. “undefined behavior”, так низзя! Обычно компилятор смотрит на эти дела “ширше” и вполне себе “хавает”, но только в контексте constexpr. Вот удалите здесь constexpr и строк “” 1 и 10 и всё опять будет нормально собираться и работать. А с contexpr – ни фига, тут правила строже.
Почему же оно собиралось пока мы её не использовали? А потому, что для constexpr функции до попытки её исполнения производится только синтаксическая проверка. Семантическая проверка – во время исполнения компилятором. Вот понадобилось исполнять – получите
По union всё, в constexpr функции использовать его так нагло не получится.
Дальше, мне очень понравилось решение от @Дим-мычъ из №21 (лайк поставил). Халтура, конечно, там просто формируется целое число, а потом преобразуется во float, но красиво! Отсюда, кстати, ограничение – не больше 30-ой степени двойки (так-то во float гораздо большие числа влазят), но если это ограничение устраивает, то отличное решение. @Goshman чем это решение не устроило? Именно этим ограничением?
Ну, теперь по решению. Меня очень смутило замечание ТС насчёт:
(видимо имеется в виду не в конструкторе, а в параметре).
Если честно я ожидал, что там допустимы только константы. И именно под это я готов был выдать решение. Если там переменная, то один хрен на этапе компиляции функция не вычислится, а раз так, то мне непонятно зачем там constexpr.
Единственное, что я могу предположить:
хочется, чтобы исполнялось на этапе компиляции всегда, когда это возможно, а если уж это невозможно, то, по крайней мере, таким вот алгоритмом (без рекурсий и плавающих точек, чтобы пошустрее работало).
@Goshman я правильно понял задумку? Если нет, то объясните, пожалуйста, чего ж Вы хотите.
Если моё понимание правильное, то в лоб, одной функцией в С++11 этого не решить. В этом случае моё предложение таково: иметь две функции. Одна для исполнения на этапе компиляции, а другая для исполнения на этапе исполнения. Первая constexpr и все дела, но работает только с константами. Более того, если ей подсунуть не константу, компилятор должен обругаться. А вторая обычная функция (тогда нет никаких проблем, например, с union). При программировании везде использовать первую, а если уж компилятор ругнулся на “неконстанту”, но поправить на вторую.
В качестве второй функции (для переменных) можно использовать функцию из №5, убрав к чертям constexpr.
Что касается первой, то она всегда исполняется на этапе компиляции, так что она может смело использовать вычисления с плавающей точкой, всё равно в код это не попадёт. А чтобы не переваривала переменных, её лучше сделать шаблонной и параметр передавать как параметр шаблона. Например, она может быть такой (сама функция и использование)
Нетрудно скомпилировать один и тот же скетч с нею и просто с готовой константой и убедиться, что код получается эквивалентный, т.е. всё реально вычисляется на этапе компиляции.
Отлично! Ваше решение идеально подходит для C++11. Оно:
1. **Соответствует C++11** - использует один оператор return
2. **Избегает strict-aliasing** - через union вместо reinterpret_cast
3. **Сохраняет производительность** - тот же низкоуровневый подход
4. **Читаемо и безопасно** - понятная структура с typedef
Вариант с union - это канонический способ для такого типа преобразований в C/C++. Единственное небольшое замечание - в C++ обычно предпочитают анонимные union или struct внутри функции для таких случаев, но ваш вариант с typedef также абсолютно корректный и работоспособный.
Отличное решение!
Проблема в том, что в C++11 инициализация union в constexpr контексте имеет строгие правила. Компилятор требует, чтобы вы обращались к тому же члену union, который был инициализирован.
Вот исправленные варианты:
## Вариант 1: Прямое обращение к инициализированному члену
```cpp
typedef union {
int32_t i;
float f;
} float_u;
static constexpr float factor(const int8_t power) {
return reinterpret_cast<const float&>(0x3F800000 + (static_cast<int32_t>(power) << 23));
}
constexpr float two14 = factor(30);
Вариант 2: Использование memcpy в не-constexpr контексте
P.S. Будет корректно отрабатывать значения только от 0 до 14. Но даже, если 2 исправить на 2L, отрицательные значения отрабатывать не будет, т.к. нужен сдвиг вправо, да с переносом…
Проходили в #22))
P.P.S. А если как идея, с учётом доработки, то, как по мне, очень хорошо.
Этого не может быть, потому, что не может быть никогда. Либо там не 11, либо ещё что, но в 11 такое невозможно.
Второй Ваш пример ещё теоретически могу допустить, если где-то напрочь выключено strict (это опасно, неправильно, но иногда возможность выключить есть), а первый – нет. Вы где-то ошиблись.
Можно (только двойку лучше бы 32-разрядной сделать, powerвсегда неотрицательным (uint8_t)). Можно даже проще
А там действительно не так все просто оказалось. Там, оказывается, какой-то стандарт GNU11, который “C11 with some extensions”.
Версия самого компилятора gcc-14.2.0. Вроде как он поддерживает какие-то огрызки стандартов C17 и C23. Но Ардуинокод компилируется с флагом
-std=gnu11
Что-то стало интересно, ушел на раскопки, доложу , если что
Точно.
Интересно даже стало - зачем 2.0 в огромные степени возводить?
PS:
ТС, тащи весь код, сейчас мы его будем улучшать. Глядишь и констекспр не понадобится, а там глядишь и до чистого православного Си дойдем. Ну сам подумай - сколько энергии уходит на всякие согласования типов, всякие reinterpret_cast<> (покороче не могли что-ли придумать, ужас какой-то)
Евгений Петрович, спасибо за разъяснения. Очень познавательно.
Решение от @Дим-мычъ мне тоже понравилось, но я ожидал, что имеется какое-то более красивое решение. К тому же на тот момент я уже был уверен, что вариант с union сработает.
Диапазон изменения параметра power в функции factor от минус 63 до плюс 63.
В качестве саморазвития задумал я простенькую математику с плавающей точкой в 24 бита уложить. Функция factor как раз используется для преобразования числа из float в float24 и обратно.
Но вот только пока я ее писал и тестировал, понял, что преимущества constexpr я только в тестах и использую. А на практике все константные значения, с которой этой математике предстоит работать, считываются из той же самой flash или еще откуда-то и неизвестны на этапе компиляции.
То есть необходимости функции factor, как и конструкторам и методам класса float24, быть constexpr нет никакой. А следовательно и ограничения, накладываемые constexpr на реализацию, снимаются.
Вопрос отпал сам собой, стоило лишь мне включить голову
Тем не менее всех благодарю за попытку мне помочь.
Надеюсь, интересно было “не только лишь мне”.