функция constexpr, отличная от UB, для сдвига с плавающей точкой или double?

#c #c 14 #bitwise-operators #type-punning

#c #c 14 #побитовые операторы #каламбур типа

Вопрос:

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

Контрольная сумма вычисляется путем выполнения исключающего ИЛИ из 16-разрядных слов сообщения из поля заголовка MSG_TYPE (включительно) до последнего поля тела сообщения

Я понял, что я не могу reinterpret_cast представить тело сообщения в виде массива uint16_t , поскольку оно выдает UB. Но я не могу использовать std::copy внутри функции constexpr, поэтому для одного сообщения у меня был рабочий и корректный подход, который использовал битовый сдвиг.

 /*!
     * brief LRF MESSAGE
     */
    struct LrfMes
    {
        //<! Message ID
        constexpr static MessageID message_id = MessageID::lrf;
        constexpr static MessageType message_type = MessageType::commandRequiringAResponse;
        constexpr static uint32_t message_length = 16;

        constexpr uint16_t calc_checksum() const
        {
            const uint16_t body[]{
                    uint16_t(uint16_t((uint8_t)lrfFire) << 8 | (uint8_t)setEcho),
                    uint16_t (uint16_t((uint8_t)setRange) << 8 | (uint8_t)setFreq),
                    uint16_t(lrfRangeMax >> 16),
                    uint16_t (lrfRangeMin),
                    uint16_t (lrfRangeMax >> 16),
                    uint16_t (lrfRangeMax)
            };
            uint16_t res = 0;

            for (size_t i = 0; i != sizeof(LrfMes) / 2;   i)
                res ^= body[i];

            return res;
        }

        LrfFire lrfFire;
        LrfSetEcho setEcho;
        LrfSetRange setRange;
        LrfSetFreq setFreq;
        uint32_t lrfRangeMin;
        uint32_t lrfRangeMax;
    };
 

Помимо того, что она доступна только со стандарта 14 (в идеале она должна быть реализована в 11), я должен написать такой метод для каждой структуры, которая у меня есть.

Но я столкнулся с проблемой, когда у меня есть float в качестве членов структуры.

 struct LosSteeringMsg
    {
        //<! Message ID
        constexpr static MessageID message_id = MessageID::losSteering;
        constexpr static MessageType message_type = MessageType::commandWithoutResponse;
        constexpr static uint32_t message_length = 32;

        constexpr uint16_t calc_checksum() const
        {
            
//            const uint16_t body[]{
//                uint16_t (uint16_t((uint8_t)steeringScr << 8) | (uint8_t)steeringMode),
//                uint16_t (uint16_t((uint8_t)resetOffset << 8) | (uint8_t)pauseScan),
//                uint16_t (reinterpret_cast<uint32_t>(azmSteering) >> 16) // this does not work
//
//            }

            uint16_t res = 0;

            return res;
        }

        LosStSCR steeringScr;
        LosStMode steeringMode;
        LosStResetOffset resetOffset;
        LosStPauseScanMes pauseScan;
        float azmSteering;
        float elevSteering;
        float azmAngle;
        float elevAngle;
        float azmSpeed;
        float elevSpeed;
    };
 

Итак, какие у меня варианты?

  • Должен ли я просто отказаться от определения этих методов как constexpr? Это действительно упростило бы мою жизнь, дав мне возможность писать только одну функцию, которая использует std::copy .
  • Используется uint32_t для хранения значений с плавающей точкой. Хотя это позволит мне вычислить контрольную сумму во время компиляции (например, для тестирования), это не будет иметь большого значения, поскольку мне нужно будет инициализировать целые числа из чисел с плавающей точкой.
  • Есть ли другой вариант??

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

1. Почему вы хотите calc_checksum быть constexpr ? Есть ли у вас constexpr LosSteeringMsg где-нибудь переменная со значениями всех элементов данных, известными во время компиляции, для которых вы хотите иметь возможность вычислять контрольную сумму во время компиляции?

2. «функция constexpr (без UB)» . Компиляторы должны диагностировать UB в контекстах contant. 🙂

3. @Jarod42 я знаю. Я упомянул об этом, потому что в других ответах есть много предложений использовать объединения или каламбур типа…

4. @IgorTandetnik если вы спрашиваете просто из любопытства, да. У меня есть. У меня есть все возможные тесты во время компиляции, включающие эти структуры, их характеристики и значения.

5. @IgorTandetnik это только ваше мнение. И я не спрашивал об этом, должен ли я делать это во время компиляции или нет. У меня есть сообщения, которые я могу создать для отправки. И я бы предпочел получить недопустимую контрольную сумму во время компиляции.

Ответ №1:

В C 20 вы можете сделать это:

 std::bit_cast<std::uint32_t>(azmSteering) >> 16;
 

До C 20 не было простого решения, которое работало бы в функции constexpr. Это не имеет UB, но независимо от этого не разрешено в функции constexpr:

 std::uint32_t u32;
std::memcpy(amp;u32, amp;azmSteering, sizeof azmSteering);
u32 >> 16;
 

Должен ли я просто отказаться от определения этих методов как constexpr?

До C 20: возможно. Технически может быть возможно извлечь правильные биты, используя операции с плавающей запятой, но это было бы довольно запутанно.

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

1. Ну, я знаю о таких прекрасных функциях C 20, но я никак не мог прыгнуть выше 14. Как я уже говорил, копирование в память облегчило бы всю контрольную сумму для каждой структуры сразу. Но я бы использовал ее только в том случае, если нет возможности использовать constexpr. Если вы заявите, что это технически невозможно сделать, я отмечу ваш ответ. Потому что я хотел бы точно знать, возможно ли это.