Skip to content

Commit 4c7e16b

Browse files
committed
✨ Add from_le and from_be
Problem: - It is counterintuitive (although representationally correct) to use `to_be` to do the job of both `htonl` and `ntohl`. Solution: - Add `from_be` and `from_le` to match `to_be` and `to_le`.
1 parent a3886fa commit 4c7e16b

File tree

3 files changed

+76
-12
lines changed

3 files changed

+76
-12
lines changed

docs/bit.adoc

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -124,18 +124,28 @@ constexpr auto y = stdx::smallest_uint<1337>(); // std::uint16_t{}
124124
using T = stdx::smallest_uint_t<1337>; // std::uint16_t
125125
----
126126

127-
=== `to_le` and `to_be`
127+
=== `to_be`, `from_be`, `to_le`, `from_le`
128128

129-
`to_le` and `to_be` are variations on `byteswap` that convert unsigned integral
130-
types to little- or big-endian respectively. On a little-endian machine, `to_le`
131-
does nothing, and `to_be` is the equivalent of `byteswap`. On a big endian
132-
machine it is the other way around.
129+
`to_be` and `from_be` are variations on `byteswap` that do the job of
130+
https://linux.die.net/man/3/htonl[`htonl` and friends]. On a big-endian machine,
131+
they do nothing, and on a little-endian machine, they are the equivalent of
132+
`byteswap`.
133+
134+
`to_le` and `from_le` do the same thing for little-endian order.
133135

134136
[source,cpp]
135137
----
136-
constexpr auto x = std::uint32_t{0x12'34'56'78};
137-
constexpr auto y = stdx::to_be(x); // 0x78'56'34'12 (on a little-endian machine)
138+
constexpr auto addr = std::uint32_t{0x7f'00'00'01}; // localhost
139+
packet.dest_addr = stdx::to_be(addr);
138140
----
139141

140-
`to_le` and `to_be` are defined for unsigned integral types. Of course for
142+
These functions are defined for unsigned integral types. Of course for
141143
`std::uint8_t` they do nothing.
144+
145+
NOTE: The implementations of `to_be` and `from_be` are identical on a machine
146+
with given endianness (either a no-op, or a byteswap). However they are provided
147+
for clarity of intent.
148+
149+
CAUTION: `from_be` is not the same as `to_le`! (And _vice versa_.) These functions are named from
150+
the point of view of the serialization format. Either we are serializing to/from
151+
big-endian, or to/from little-endian.

include/stdx/bit.hpp

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,26 @@ to_be(T x) noexcept -> std::enable_if_t<std::is_unsigned_v<T>, T> {
259259
}
260260
}
261261

262+
template <typename T>
263+
[[nodiscard]] constexpr auto
264+
from_le(T x) noexcept -> std::enable_if_t<std::is_unsigned_v<T>, T> {
265+
if constexpr (stdx::endian::native == stdx::endian::big) {
266+
return byteswap(x);
267+
} else {
268+
return x;
269+
}
270+
}
271+
272+
template <typename T>
273+
[[nodiscard]] constexpr auto
274+
from_be(T x) noexcept -> std::enable_if_t<std::is_unsigned_v<T>, T> {
275+
if constexpr (stdx::endian::native == stdx::endian::little) {
276+
return byteswap(x);
277+
} else {
278+
return x;
279+
}
280+
}
281+
262282
template <typename To>
263283
constexpr auto bit_pack = [](auto... args) {
264284
static_assert(stdx::always_false_v<To, decltype(args)...>,
@@ -313,10 +333,8 @@ template <typename To, typename From> constexpr auto bit_unpack(From arg) {
313333

314334
constexpr auto sz = sized<From>{1}.template in<To>();
315335
auto r = bit_cast<std::array<To, sz>>(to_be(arg));
316-
if constexpr (stdx::endian::native == stdx::endian::little) {
317-
for (auto &elem : r) {
318-
elem = byteswap(elem);
319-
}
336+
for (auto &elem : r) {
337+
elem = from_be(elem);
320338
}
321339
return r;
322340
}

test/bit.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,42 @@ TEST_CASE("to big endian", "[bit]") {
5151
}
5252
}
5353

54+
TEST_CASE("from little endian", "[bit]") {
55+
static_assert(stdx::from_le(std::uint8_t{1u}) == 1u);
56+
57+
[[maybe_unused]] constexpr std::uint16_t u16{0x1234};
58+
[[maybe_unused]] constexpr std::uint32_t u32{0x1234'5678};
59+
[[maybe_unused]] constexpr std::uint64_t u64{0x1234'5678'9abc'def0};
60+
61+
if constexpr (stdx::endian::native == stdx::endian::little) {
62+
CHECK(stdx::from_le(u16) == u16);
63+
CHECK(stdx::from_le(u32) == u32);
64+
CHECK(stdx::from_le(u64) == u64);
65+
} else if constexpr (stdx::endian::native == stdx::endian::big) {
66+
CHECK(stdx::from_le(u16) == stdx::byteswap(u16));
67+
CHECK(stdx::from_le(u32) == stdx::byteswap(u32));
68+
CHECK(stdx::from_le(u64) == stdx::byteswap(u64));
69+
}
70+
}
71+
72+
TEST_CASE("from big endian", "[bit]") {
73+
static_assert(stdx::from_be(std::uint8_t{1u}) == 1u);
74+
75+
[[maybe_unused]] constexpr std::uint16_t u16{0x1234};
76+
[[maybe_unused]] constexpr std::uint32_t u32{0x1234'5678};
77+
[[maybe_unused]] constexpr std::uint64_t u64{0x1234'5678'9abc'def0};
78+
79+
if constexpr (stdx::endian::native == stdx::endian::big) {
80+
CHECK(stdx::from_be(u16) == u16);
81+
CHECK(stdx::from_be(u32) == u32);
82+
CHECK(stdx::from_be(u64) == u64);
83+
} else if constexpr (stdx::endian::native == stdx::endian::little) {
84+
CHECK(stdx::from_be(u16) == stdx::byteswap(u16));
85+
CHECK(stdx::from_be(u32) == stdx::byteswap(u32));
86+
CHECK(stdx::from_be(u64) == stdx::byteswap(u64));
87+
}
88+
}
89+
5490
TEMPLATE_TEST_CASE("popcount", "[bit]", std::uint8_t, std::uint16_t,
5591
std::uint32_t, std::uint64_t) {
5692
static_assert(stdx::popcount(TestType{}) == 0);

0 commit comments

Comments
 (0)