Search code examples
c++c++11endiannessconstexpr

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
    {
                // ENDIAN is defined via CMake TestBigEndian
        constexpr bool native_is_big_endian()
        {
#ifdef ENDIAN
            return true;
#else
            return false;
#endif
        }
    }

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


Solution

  • You are ignoring the third argument passed into the recurrent overload of bitwise_or through the res parameter. It seem to work if

    return bitwise_or(tag_s<i - 1>(), original,
      original << i * 8 >> sz_minus_one * 8 << i * 8);
    

    is changed to:

    return bitwise_or(tag_s<i - 1>(), original,
      res | original << i * 8 >> sz_minus_one * 8 << i * 8);
    

    Live demo: https://godbolt.org/z/xW81z4