jeudi 26 novembre 2020

template constexpr endian converter (without UB)

I have seen some other answers that propose to use unions for byte swapping (which is UB or can't be done at compile time).

I've wrote mine, and it kind of worked until I have met some case which have shown that my implementation is invalid. I can't spot a mistake, can you help me?

namespace impl
    {
        constexpr bool native_is_big_endian()
        {
            return bool(uint16_t(1) >> 8u);
        }
    }

    /*!
     * \brief std compliant type for endianness
     * \details
     * If all scalar types are little-endian, endian::native equals endian::little
     * If all scalar types are big-endian, endian::native equals endian::big
     */
    enum class endian
    {
        little,
        big,
        native = impl::native_is_big_endian() ? big : little
    };

    template<typename T>
    class swap_endian
    {
        constexpr static size_t sz_minus_one = sizeof(T) - 1;
        template<size_t> struct tag_s
        {
        };

        constexpr static T bitwise_or(tag_s<0>, T original, T res)
        {
            return res | (original >> sz_minus_one * 8);
        }

        template<size_t i>
        constexpr static T bitwise_or(tag_s<i>, T original, T res)
        {
            return bitwise_or(tag_s<i - 1>(), original, original << i * 8 >> sz_minus_one * 8 << i * 8);
        }

    public:
        constexpr static T swap(T u)
        {
            return bitwise_or(tag_s<sz_minus_one>(), u, 0);
        }
    };

    template<typename T>
    constexpr T swap_endian_v(T u)
    {
        return swap_endian<T>::swap(u);
    }

    template<endian From, typename T>
    constexpr T to_native_endian(T u)
    {
        return From == endian::native ? u : swap_endian_v(u);
    }

int main()
{
    static_assert(uint8_t(0xFA) == swap_endian_v(uint8_t(0xFA)), "Invalid result for endian swapping");
    static_assert(uint16_t(0x00AA) == swap_endian_v(uint16_t(0xAA00)), "Invalid result for endian swapping");
    static_assert(uint16_t(0xF0AA) == swap_endian_v(uint16_t(0xAAF0)), "Invalid result for endian swapping");
    static_assert(uint32_t(0x00'00'CC'00) == swap_endian_v(uint32_t(0x00'CC'00'00)),
                  "Invalid result for endian swapping");

// this fails
//    static_assert(uint32_t(0x6A'25'65'75) == swap_endian_v(uint32_t(0x75'65'25'6A)),
//                  "Invalid result for endian swapping");
    return 0;
}

Please, don't suggest to use BOOST. At this point I am very interested in finding out what kind of mistake I've made in the algorithm.

Aucun commentaire:

Enregistrer un commentaire