non-UB constexpr function to bitshift a float or double?

175 Views Asked by At

I want a constexpr function (without UB) to calculate a checksum of a message. According to the specification

The Checksum is computed by running an exclusive OR of the 16-bit words of the message from header field MSG_TYPE (inclusive) up to the last field of the message body

I've figured out, that I can't reinterpret_cast the message body as an array of uint16_t, as it yields UB. But I cannot use std::copy inside a constexpr function, so for one message I had a working and valid approach that used bitshifting.

/*!
     * \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;
    };

Apart from being available only since 14 standard (ideally it must be implemented in 11), I have to write such method for each and every structure I have.

But I have faced a problem when I have floats as struct members.

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;
    };

So, what are my options?

  • Should I just give up on defining these methods as constexpr? This would actually simplify my life, giving me an ability to write only one function that uses std::copy.
  • Use uint32_t to store float values. Though it will allow me to calculate a checksum at compile time (for testing, for example), it wouldn't make much difference since I'll need to initialize integers from floats.
  • Any other option??
1

There are 1 best solutions below

1
On

In C++20, you can do this:

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

Prior to C++20 there is no simple solution that works in a constexpr function. This has no UB, but is regardless not allowed in constexpr function:

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

Should I just give up on defining these methods as constexpr?

Prior to C++20: Maybe. Technically it may be possible to extract the correct bits using floating point operations but it would be quite convoluted.