Constexpr функция на с++11

Вот так, вручную можно.)))
Только для неотрицательных значений power.
Если будет время, отрицательные позже “прикручу”))

static constexpr float factor( int32_t power) {
 
  return power == 0 ? 1 : (((0x7FFFFFL&(0x3F800000L + (power << 23))) + 1) * \
  (float)(2L <<((((0x3F800000L + (power  << 23))& 0x7F800000L) >> 23) - 1) - 127));
}

Тип int8_t сразу, в параметрах функции приводится

Пока Евгений Петрович не ответил, можно и поиграться))

static constexpr float factor( int8_t power) {
 
  return power == 0 ? 1 : (power > 0 ? \
  (((0x7FFFFFL&(0x3F800000L + ((int32_t)power << 23))) + 1) * \
  (float)(2L <<((((0x3F800000L + ((int32_t)power  << 23))& 0x7F800000L) >> 23) - 1) - 127)) \ 
  : 1.0/(((0x7FFFFFL&(0x3F800000L + ((int32_t)(power * -1) << 23))) + 1) * \
  (float)(2L <<((((0x3F800000L + ((int32_t)(power * -1)  << 23))& 0x7F800000L) >> 23) - 1) - 127)));
}

P.S. Тщательно код не проверял, т.к. это больше как шутка.
В Wokwi вроде работает

Спойлер

Добрый вечер!

Итак по вариантам.
Вариант 1

static constexpr float factor(const int8_t power) {
    return *reinterpret_cast<float*>(&(int32_t{0x3F800000 + (static_cast<int32_t>(power) << 23)}));
}

Собирается с++11 и с++14 с одинаковым предупреждением, но работает.

warning: taking address of temporary [-fpermissive]
return reinterpret_cast<float>(&(int32_t{0x3F800000 + (static_cast<int32_t>(power) << 23)}));

Вариант 2

static constexpr float factor(const int8_t power) {
    return (union { int32_t i; float f; }){0x3F800000 + (static_cast<int32_t>(power) << 23)}.f;
}

Собирается только с с++14 и работает.
С с++11 выдает ошибку сборки:

error: body of constexpr function ‘constexpr float factor(int8_t)’ not a return-statement

Вариант 3

static constexpr float factor(const int8_t power) {
    return *reinterpret_cast<const float*>(
        static_cast<const void*>(
            &static_cast<const int32_t>(0x3F800000 + (static_cast<int32_t>(power) << 23))
        )
    );
}

Не собирается.
В с++11 ошибки:

error: lvalue required as unary ‘&’ operand
error: body of constexpr function ‘constexpr float factor(int8_t)’ not a return-statement

В с++14:

error: lvalue required as unary ‘&’ operand

Функция используется как с константами в конструкторе, так и с переменными.

Ждём, что скажет @ЕвгенийП !
Как бы Интеллект против ИИ )))

В этом случае функцию не вызвать из constexpr коструктора.

Вот в таком виде собралось с с++11:

typedef union {
  int32_t i;
  float f;
} float_u;

static constexpr float factor(const int8_t power) {
  return float_u { 0x3F800000 + (static_cast<int32_t>(power) << 23) }.f;
}

Так, прошу прощения, собирался утром ответить, но … понедельник … вот только сейчас собрался. Извините.

Так, ну портянку от “обобщённой алисы” я комментировать не буду, пусть она свой бред сама комментирует.

Давайте по делу.

Здесь мало проверять компилятором саму функцию, надо проверять ещё и её использование. Например, Вы пишете:

Да, но есть нюанс! Собирается пока не используется!

Давайте проверим с С++ 14 сначала без использования:

static constexpr float factor(const int8_t power) {
	union {
		int32_t i;
		float f;
	} converter {0};
	converter.i = 0x3F800000 + (static_cast<int32_t>(power) << 23);
	return converter.f;
}

//constexpr float two14 = factor(14);

void setup(void) {
	Serial.begin(9600);
//	Serial.println(two14);
}

void loop(void){}

Всё прекрасно, собирается!

А теперь раскомментируем строки №№ 10 и 14 … опс … перестала собираться :frowning:

В чём тут проблема?

Дело в том, что проинициализировать одно поле union, а использовать другое – это т.н. “undefined behavior”, так низзя! Обычно компилятор смотрит на эти дела “ширше” и вполне себе “хавает”, но только в контексте constexpr. Вот удалите здесь constexpr и строк “” 1 и 10 и всё опять будет нормально собираться и работать. А с contexpr – ни фига, тут правила строже.

Почему же оно собиралось пока мы её не использовали? А потому, что для constexpr функции до попытки её исполнения производится только синтаксическая проверка. Семантическая проверка – во время исполнения компилятором. Вот понадобилось исполнять – получите :frowning:

По union всё, в constexpr функции использовать его так нагло не получится.

Дальше, мне очень понравилось решение от @Дим-мычъ из №21 (лайк поставил). Халтура, конечно, там просто формируется целое число, а потом преобразуется во float, но красиво! Отсюда, кстати, ограничение – не больше 30-ой степени двойки (так-то во float гораздо большие числа влазят), но если это ограничение устраивает, то отличное решение. @Goshman чем это решение не устроило? Именно этим ограничением?

Ну, теперь по решению. Меня очень смутило замечание ТС насчёт:

(видимо имеется в виду не в конструкторе, а в параметре).

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

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

@Goshman я правильно понял задумку? Если нет, то объясните, пожалуйста, чего ж Вы хотите.

Если моё понимание правильное, то в лоб, одной функцией в С++11 этого не решить. В этом случае моё предложение таково: иметь две функции. Одна для исполнения на этапе компиляции, а другая для исполнения на этапе исполнения. Первая constexpr и все дела, но работает только с константами. Более того, если ей подсунуть не константу, компилятор должен обругаться. А вторая обычная функция (тогда нет никаких проблем, например, с union). При программировании везде использовать первую, а если уж компилятор ругнулся на “неконстанту”, но поправить на вторую.

В качестве второй функции (для переменных) можно использовать функцию из №5, убрав к чертям constexpr.

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

template<const uint8_t power> constexpr float factor(void) { return 2.0 * factor<power-1>(); }
template<> constexpr float factor<0>(void) { return 1.0; }

constexpr float two14 = factor<14>();

Нетрудно скомпилировать один и тот же скетч с нею и просто с готовой константой и убедиться, что код получается эквивалентный, т.е. всё реально вычисляется на этапе компиляции.

“Папы, что сказали эти, кандидаты в доктора” -

Отлично! Ваше решение идеально подходит для C++11. Оно:

1. **Соответствует C++11** - использует один оператор return
2. **Избегает strict-aliasing** - через union вместо reinterpret_cast
3. **Сохраняет производительность** - тот же низкоуровневый подход
4. **Читаемо и безопасно** - понятная структура с typedef

Вариант с union - это канонический способ для такого типа преобразований в C/C++. Единственное небольшое замечание - в C++ обычно предпочитают анонимные union или struct внутри функции для таких случаев, но ваш вариант с typedef также абсолютно корректный и работоспособный.

Отличное решение!

Простите, не заметил, но для этого решения верно то, что я писал выше. Оно собралось пока не используется. А попробуйте использовать:

typedef union {
  int32_t i;
  float f;
} float_u;

static constexpr float factor(const int8_t power) {
  return float_u { 0x3F800000 + (static_cast<int32_t>(power) << 23) }.f;
}

constexpr float two14 = factor(30);

void setup(void) {
	Serial.begin(9600);
	Serial.println(two14);
}

void loop(void){}

Получите вот такую ошибку:

sketch.ino:10:34: error: accessing 'float_u::f' member instead of initialized 'float_u::i' member in constant expression

Что пишет ИИ

Проблема в том, что в 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 контексте

#include <cstring>

static float factor(const int8_t power) {
  int32_t tmp = 0x3F800000 + (static_cast<int32_t>(power) << 23);
  float result;
  memcpy(&result, &tmp, sizeof(result));
  return result;
}

float two14 = factor(30); // не constexpr

Вариант 3: Математический подход (надежный для constexpr)

static constexpr float factor(const int8_t power) {
    return power == 0 ? 1.0f : 
           power > 0 ? 2.0f * factor(power - 1) : 
           0.5f * factor(power + 1);
}

constexpr float two14 = factor(30);

Вариант 4: Использование встроенных функций

#include <cmath>

static constexpr float factor(const int8_t power) {
    // Для небольших степеней можно использовать таблицу
    constexpr float powers[] = {
        1.0f, 2.0f, 4.0f, 8.0f, 16.0f, 32.0f, 64.0f, 128.0f, 256.0f, 512.0f,
        1024.0f, 2048.0f, 4096.0f, 8192.0f, 16384.0f, 32768.0f, 65536.0f
    };
    return power >= 0 ? powers[power] : 1.0f / powers[-power];
}

constexpr float two14 = factor(14);

Рекомендация: Используйте Вариант 3 (математический) для constexpr или Вариант 2 если не нужна constexpr оценка во время компиляции.

Евгений Петрович, завтра вечером подробно отвечу.
Надо выспаться перед работой )

Оттакот собирается: (И работает! По крайней мере на ЕСП32, у которого конпилятор как раз С11)

constexpr float pow2dot0(int8_t power) {
    union { uint32_t uint; float fl; } u = { 0x3F800000u + (uint32_t(power) << 23) };
    return u.fl;
}

Если только один return нужен, то (этот код я не проверял, но почему бы ему и не заработать?):

constexpr float pow2dot0(int8_t power) {
    
    return ({
         union { uint32_t uint; float fl; } u = { 0x3F800000u + (uint32_t(power) << 23) };
         u.fl;
    });
}

Но.

Но я бы сделал, как в посте #61

Извиняюсь, у нас тут вопрос из зала.

Может я чего не понимаю, но почему нельзя было сделать так:

constexpr float pow2dot0(int8_t power) {
    return power ? float(2 << (power - 1)) : 1.0f;
}

Вроде работает :slight_smile:

Вряд ли. В работе проверяли?

P.S. Будет корректно отрабатывать значения только от 0 до 14. Но даже, если 2 исправить на 2L, отрицательные значения отрабатывать не будет, т.к. нужен сдвиг вправо, да с переносом…
Проходили в #22))
P.P.S. А если как идея, с учётом доработки, то, как по мне, очень хорошо.

Этого не может быть, потому, что не может быть никогда. Либо там не 11, либо ещё что, но в 11 такое невозможно.

Второй Ваш пример ещё теоретически могу допустить, если где-то напрочь выключено strict (это опасно, неправильно, но иногда возможность выключить есть), а первый – нет. Вы где-то ошиблись.

Можно (только двойку лучше бы 32-разрядной сделать, power всегда неотрицательным (uint8_t)). Можно даже проще

constexpr float factor(const uint8_t power) {
   return float (1ul << power);
}

Только результат будет ограничен 2^31. А во float влазят гораздо большие числа. Если так ограничивать, то зачем вообще огород с float городить?

Собственно, если убрать обфускацию, то принципиально именно такое решение предлагал @Дим-мычъ выше.

Какой диапазон степеней? [0..255] ?

А там действительно не так все просто оказалось. Там, оказывается, какой-то стандарт 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 на реализацию, снимаются.

Вопрос отпал сам собой, стоило лишь мне включить голову :slight_smile:
Тем не менее всех благодарю за попытку мне помочь.
Надеюсь, интересно было “не только лишь мне”.