конечный преобразователь шаблонов constexpr (без UB)

#c #c 11 #endianness #constexpr

#c #c 11 #порядковый номер #constexpr

Вопрос:

Я видел несколько других ответов, в которых предлагается использовать объединения для замены байтов (что является UB или не может быть выполнено во время компиляции).

Я написал свой, и это вроде как работало, пока я не встретил какой-то случай, который показал, что моя реализация недействительна. Я не могу обнаружить ошибку, вы можете мне помочь?

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

 

Пожалуйста, не предлагайте использовать BOOST.
На данный момент мне очень интересно выяснить, какую ошибку я допустил в алгоритме.

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

1. Не uint16_t(1) >> 8u будет ли значение равно 0 независимо от порядкового номера текущей архитектуры? Я не думаю, что существует какой-либо переносимый тест времени компиляции на std::endian порядковый номер до C 20.

2. @DanielLangr спасибо, что указали на это!

3. Вывод swap_endian_v(uint32_t(0x75'65'25'6A)) 6575 — это то, что означает, что байты более высокого порядка отбрасываются

4. @Harry Я в курсе этого. Я не вижу ошибки в коде.

5. Я голосую за закрытие этого вопроса, потому что он требует пересмотра существующего кода, а не решения конкретной проблемы.

Ответ №1:

Вы игнорируете третий аргумент, переданный в повторяющуюся перегрузку bitwise_or через res параметр. Кажется, это работает, если

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

изменяется на:

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

Живая демонстрация: https://godbolt.org/z/xW81z4