From a compiler perspective, using a function makes the most sense, since that fits into the existing control-flow analysis that the compiler will do. Pragmas are processed by the pre-processor, so they aren't appropriate for expressing control flow hints.
2. `std::to_underlying(t)` is a wrapper around `static_cast<std::underlying_type_t<std::remove_cv_t<std::remove_reference_t<decltype(t)>>>>(t)`. So a lot fewer characters. It's also useful since `std::underlying_type_t<T>` behaves weirdly if `T` is not an enum type.
I think you are maybe missing the context that C++ allows the representation of an enum to be defined, e.g. `enum class X : unsigned char {};` vs `enum class Y : unsigned long long {};`. So you can't always cast to `int`. Technically, this isn't the case in C either: the type defaults to `int`, but the compiler will pick a larger type if necessary, e.g. `enum Z { a = ((long long)INT_MAX) + 1 };`
3. `htonl` are not standardized, so they were not part of the C++ standard library. Also, on Windows, I believe you'd need to include `winsock.h` to get access to them, which has its own idiosyncratic issues. You are also missing the context of C++ defining operator overloading, so you can call `std::byteswap(0ull)` and get an `unsigned long long` and you can call `std::byteswap(std::uint16_t{0})` and get a 16 bit unsigned integer.
[0]: https://stackoverflow.com/questions/60802864/emulating-gccs-...