Byte Conversions
Description
The library provides functions for converting safe integer types to and from big-endian or little-endian byte order.
These operate on the non-bounded unsigned types (u8, u16, u32, u64, u128).
For big-endian conversions (to_be/from_be): on big-endian platforms these are no-ops; on little-endian platforms they delegate to byteswap.
For little-endian conversions (to_le/from_le): on little-endian platforms these are no-ops; on big-endian platforms they delegate to byteswap.
#include <boost/safe_numbers/byte_conversions.hpp>
to_be
from_be
Converts a value from big-endian byte order to the native byte order.
This is the inverse of to_be.
Since byte swapping is its own inverse, from_be delegates directly to to_be.
template <non_bounded_integral_library_type T>
[[nodiscard]] constexpr auto from_be(const T value) noexcept -> T;
to_le
Converts a value from the native byte order to little-endian byte order.
template <non_bounded_integral_library_type T>
[[nodiscard]] constexpr auto to_le(const T value) noexcept -> T;
from_le
Converts a value from little-endian byte order to the native byte order.
This is the inverse of to_le.
Since byte swapping is its own inverse, from_le delegates directly to to_le.
template <non_bounded_integral_library_type T>
[[nodiscard]] constexpr auto from_le(const T value) noexcept -> T;
to_be_bytes
Converts a safe integer value to a big-endian byte array.
The value is first converted to big-endian byte order using to_be, then reinterpreted as an array of std::byte.
template <non_bounded_integral_library_type T>
BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto to_be_bytes(const T value) noexcept -> std::array<std::byte, sizeof(T)>;
from_be_bytes
Reconstructs a safe integer value from a big-endian byte array.
The bytes are reinterpreted as the underlying type and then converted from big-endian to native byte order using from_be.
template <non_bounded_integral_library_type T, std::size_t N>
BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto from_be_bytes(const std::span<const std::byte, N> bytes) -> T;
Parameters
-
bytes— A span of bytes in big-endian order. May have a fixed extent orstd::dynamic_extent.
Extent Matching
The function validates that the number of input bytes matches sizeof(T):
-
Fixed extent matches
sizeof(T): Compiles and executes normally. -
Fixed extent does not match
sizeof(T): Produces astatic_assertfailure at compile time. -
Dynamic extent: Checks at runtime and throws
std::domain_errorif the sizes do not match.
to_le_bytes
Converts a safe integer value to a little-endian byte array.
The value is first converted to little-endian byte order using to_le, then reinterpreted as an array of std::byte.
template <non_bounded_integral_library_type T>
BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto to_le_bytes(const T value) noexcept -> std::array<std::byte, sizeof(T)>;
from_le_bytes
Reconstructs a safe integer value from a little-endian byte array.
The bytes are reinterpreted as the underlying type and then converted from little-endian to native byte order using from_le.
template <non_bounded_integral_library_type T, std::size_t N>
BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto from_le_bytes(const std::span<const std::byte, N> bytes) -> T;
Parameters
-
bytes— A span of bytes in little-endian order. May have a fixed extent orstd::dynamic_extent.
Extent Matching
The function validates that the number of input bytes matches sizeof(T):
-
Fixed extent matches
sizeof(T): Compiles and executes normally. -
Fixed extent does not match
sizeof(T): Produces astatic_assertfailure at compile time. -
Dynamic extent: Checks at runtime and throws
std::domain_errorif the sizes do not match.
to_ne_bytes
Converts a safe integer value to a native-endian byte array.
Delegates to to_le_bytes on little-endian platforms and to_be_bytes on big-endian platforms.
The result is equivalent to std::bit_cast<std::array<std::byte, sizeof(T)>>(value) — i.e., the raw in-memory representation.
template <non_bounded_integral_library_type T>
BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto to_ne_bytes(const T value) noexcept -> std::array<std::byte, sizeof(T)>;
from_ne_bytes
Reconstructs a safe integer value from a native-endian byte array.
Delegates to from_le_bytes on little-endian platforms and from_be_bytes on big-endian platforms.
template <non_bounded_integral_library_type T, std::size_t N>
BOOST_SAFE_NUMBERS_HOST_DEVICE [[nodiscard]] constexpr auto from_ne_bytes(const std::span<const std::byte, N> bytes) -> T;
Parameters
-
bytes— A span of bytes in native byte order. May have a fixed extent orstd::dynamic_extent.
Extent Matching
The function validates that the number of input bytes matches sizeof(T):
-
Fixed extent matches
sizeof(T): Compiles and executes normally. -
Fixed extent does not match
sizeof(T): Produces astatic_assertfailure at compile time. -
Dynamic extent: Checks at runtime and throws
std::domain_errorif the sizes do not match.
Examples
// Copyright 2026 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
#include <boost/safe_numbers/byte_conversions.hpp>
#include <boost/safe_numbers/unsigned_integers.hpp>
#include <boost/safe_numbers/iostream.hpp>
#include <iostream>
#include <iomanip>
#include <cstddef>
#include <cstdint>
#include <span>
int main()
{
using namespace boost::safe_numbers;
// ---- to_be / from_be / to_le / from_le: scalar byte-order round-trips ----
// The direct result of to_be / to_le is platform dependent, but a round-trip
// back through from_be / from_le always recovers the original value.
std::cout << "=== scalar byte-order round-trips ===\n";
{
const auto value = u32{0x01020304U};
std::cout << std::hex;
std::cout << "from_be(to_be(0x01020304)) = 0x" << from_be(to_be(value)) << '\n';
std::cout << "from_le(to_le(0x01020304)) = 0x" << from_le(to_le(value)) << '\n';
std::cout << std::dec;
}
std::cout << '\n';
// ---- to_be_bytes: convert to big-endian byte array ----
std::cout << "=== to_be_bytes ===\n";
{
const auto bytes = to_be_bytes(u32{0x01020304U});
std::cout << "u32(0x01020304) -> BE bytes: ";
for (const auto& b : bytes)
{
std::cout << std::hex << std::setfill('0') << std::setw(2)
<< static_cast<unsigned>(b) << ' ';
}
std::cout << '\n';
}
{
const auto bytes = to_be_bytes(u16{static_cast<std::uint16_t>(0xABCD)});
std::cout << "u16(0xABCD) -> BE bytes: ";
for (const auto& b : bytes)
{
std::cout << std::hex << std::setfill('0') << std::setw(2)
<< static_cast<unsigned>(b) << ' ';
}
std::cout << '\n';
}
// ---- from_be_bytes: reconstruct from big-endian bytes ----
std::cout << "\n=== from_be_bytes ===\n";
{
const std::array<std::byte, 4> bytes {
std::byte{0x01}, std::byte{0x02}, std::byte{0x03}, std::byte{0x04}
};
const auto val = from_be_bytes<u32>(std::span<const std::byte, 4>{bytes});
std::cout << "BE bytes {01,02,03,04} -> u32: 0x"
<< std::hex << static_cast<std::uint32_t>(val) << '\n';
}
// ---- to_le_bytes: convert to little-endian byte array ----
std::cout << "\n=== to_le_bytes ===\n";
{
const auto bytes = to_le_bytes(u32{0x01020304U});
std::cout << "u32(0x01020304) -> LE bytes: ";
for (const auto& b : bytes)
{
std::cout << std::hex << std::setfill('0') << std::setw(2)
<< static_cast<unsigned>(b) << ' ';
}
std::cout << '\n';
}
{
const auto bytes = to_le_bytes(u64{0x0102030405060708ULL});
std::cout << "u64(0x01..08) -> LE bytes: ";
for (const auto& b : bytes)
{
std::cout << std::hex << std::setfill('0') << std::setw(2)
<< static_cast<unsigned>(b) << ' ';
}
std::cout << '\n';
}
// ---- from_le_bytes: reconstruct from little-endian bytes ----
std::cout << "\n=== from_le_bytes ===\n";
{
const std::array<std::byte, 4> bytes {
std::byte{0x04}, std::byte{0x03}, std::byte{0x02}, std::byte{0x01}
};
const auto val = from_le_bytes<u32>(std::span<const std::byte, 4>{bytes});
std::cout << "LE bytes {04,03,02,01} -> u32: 0x"
<< std::hex << static_cast<std::uint32_t>(val) << '\n';
}
// ---- to_ne_bytes / from_ne_bytes: native endian round-trip ----
std::cout << "\n=== to_ne_bytes / from_ne_bytes (native endian) ===\n";
{
const auto original = u32{0xDEADBEEFU};
const auto bytes = to_ne_bytes(original);
std::cout << "u32(0xDEADBEEF) -> NE bytes: ";
for (const auto& b : bytes)
{
std::cout << std::hex << std::setfill('0') << std::setw(2)
<< static_cast<unsigned>(b) << ' ';
}
std::cout << '\n';
const auto reconstructed = from_ne_bytes<u32>(std::span<const std::byte, 4>{bytes});
std::cout << "Round-trip: -> u32: 0x"
<< std::hex << static_cast<std::uint32_t>(reconstructed) << '\n';
}
// ---- BE round-trip with u8 ----
std::cout << "\n=== u8 round-trip ===\n";
{
const auto original = u8{0x42};
const auto be = to_be_bytes(original);
const auto le = to_le_bytes(original);
std::cout << "u8(0x42) -> BE: " << std::hex << std::setfill('0') << std::setw(2)
<< static_cast<unsigned>(be[0]) << '\n';
std::cout << "u8(0x42) -> LE: " << std::hex << std::setfill('0') << std::setw(2)
<< static_cast<unsigned>(le[0]) << '\n';
}
return 0;
}
Output:
=== scalar byte-order round-trips ===
from_be(to_be(0x01020304)) = 0x1020304
from_le(to_le(0x01020304)) = 0x1020304
=== to_be_bytes ===
u32(0x01020304) -> BE bytes: 01 02 03 04
u16(0xABCD) -> BE bytes: ab cd
=== from_be_bytes ===
BE bytes {01,02,03,04} -> u32: 0x1020304
=== to_le_bytes ===
u32(0x01020304) -> LE bytes: 04 03 02 01
u64(0x01..08) -> LE bytes: 08 07 06 05 04 03 02 01
=== from_le_bytes ===
LE bytes {04,03,02,01} -> u32: 0x1020304
=== to_ne_bytes / from_ne_bytes (native endian) ===
u32(0xDEADBEEF) -> NE bytes: ef be ad de
Round-trip: -> u32: 0xdeadbeef
=== u8 round-trip ===
u8(0x42) -> BE: 42
u8(0x42) -> LE: 42