refactor(utils): trim unused helpers, make deferrer allocation-free

Remove app::utils helpers with zero fleet call sites after the app_common
merge (as_u8s, hexdump, add_overflow/overflow_error, try_get/try_get_ref,
constrain_value_in_range_static — a template-bounds reimplementation of
std::clamp) and the unused app_clock milliseconds alias. This drops several
heavy includes from a header every consumer compiles.

Rewrite deferrer as a stdlib scope guard that owns the callable directly
(template<F>, non-copyable/non-movable) instead of type-erasing through
std::move_only_function/std::function, so it never heap-allocates. The 3
call sites (band max32664d) are unchanged via CTAD.
This commit is contained in:
2026-07-15 22:32:04 +08:00
parent c948c7565c
commit 1da86dd657
3 changed files with 12 additions and 173 deletions
+3 -4
View File
@@ -6,10 +6,9 @@ hand-copied (and had drifted) between the three repos.
## Contents
- `inc/app_utils.hpp` — byte/span casts (`as_bytes`, `as_u8s`), `overloads`,
`try_get`/`try_get_ref`, `deferrer`, `hexdump`, `add_overflow`,
`constrain_value_in_range_static`, `to_mac_string` (only when the toolchain
has `<format>`; guarded by `__cpp_lib_format`).
- `inc/app_utils.hpp` — byte/span casts (`as_bytes`), `overloads`, `deferrer`,
`to_mac_string` (only when the toolchain has `<format>`; guarded by
`__cpp_lib_format`).
- `inc/app_clock.hpp` — monotonic microsecond `TrivialClock`. The only
OS-specific code in this repo: `now()` is `k_uptime_ticks()` on Zephyr and
`esp_timer_get_time()` on ESP-IDF, selected by `__ZEPHYR__` / `ESP_PLATFORM`.
-1
View File
@@ -34,5 +34,4 @@ struct clock_t {
#endif
}
};
using milliseconds = std::chrono::duration<int32_t, std::milli>;
}
+9 -168
View File
@@ -1,15 +1,8 @@
#pragma once
#include <cstdint>
#include <cstdio>
#include <expected>
#include <functional>
#include <limits>
#include <optional>
#include <ranges>
#include <span>
#include <tuple>
#include <variant>
#include <utility>
#include <version>
#ifdef __cpp_lib_format
#include <format>
@@ -61,182 +54,30 @@ inline std::span<const std::byte> as_bytes(const std::uint8_t *data, std::size_t
return {reinterpret_cast<const std::byte *>(data), size};
}
/// @brief reinterpret_cast a trivially copyable value to a span of uint8_t
template <typename T>
requires std::is_trivially_copyable_v<T>
inline std::span<const std::uint8_t> as_u8s(const T &value) {
return {reinterpret_cast<const std::uint8_t *>(&value), sizeof(value)};
}
/// @brief reinterpret_cast a trivially copyable value to a span of uint8_t span
template <typename T>
requires std::is_trivially_copyable_v<T>
inline std::span<std::uint8_t> as_u8s(T &value) {
return {reinterpret_cast<std::uint8_t *>(&value), sizeof(value)};
}
/// @brief convert a std::byte span to a uint8_t span
inline std::span<const std::uint8_t> as_u8s(std::span<const std::byte> span) {
return {reinterpret_cast<const std::uint8_t *>(span.data()), span.size()};
}
/// @brief convert a std::byte span to a uint8_t span (mutable)
inline std::span<std::uint8_t> as_u8s(std::span<std::byte> span) {
return {reinterpret_cast<std::uint8_t *>(span.data()), span.size()};
}
/// @brief helper type for the visitor
template <class... Ts>
struct overloads : Ts... {
using Ts::operator()...;
};
inline void hexdump(std::span<const std::byte> data) {
const auto enumerate = [](const auto &data) {
return data | std::views::transform([i = 0](const auto &value) mutable {
return std::make_tuple(i++, value);
});
};
for (const auto [i, byte] : enumerate(data)) {
bool is_end = i == data.size() - 1;
if (is_end) {
printf("%02x\n", static_cast<uint8_t>(byte));
} else {
if (i % 16 == 15) {
printf("%02x\n", static_cast<uint8_t>(byte));
} else {
printf("%02x ", static_cast<uint8_t>(byte));
}
}
}
}
/**
* @brief try to get an element from a span
* @tparam T, should be trivially copyable
* @param s span
* @param idx index, can be negative, will be shifted into [0, s.size())
* @return std::optional<T> element, or std::nullopt if index is out of range
*/
template <typename T>
std::optional<T> try_get(std::span<T> s, int idx) {
const int n = static_cast<int>(s.size());
// 1) if negative, shift into [n, 0) → [0, n)
if (idx < 0) {
idx += n;
}
// 2) now both negative-too-big and positive-too-big land outside [0,n)
if (idx < 0 || idx >= n) {
return std::nullopt;
}
return s[idx];
}
/**
* @brief try to get an element from a const span
* @tparam T
* @param s const span
* @param idx index, can be negative, will be shifted into [0, s.size())
* @return std::optional<std::reference_wrapper<const T>> element reference, or std::nullopt if index is out of range
* @see try_get
*/
template <typename T>
std::optional<std::reference_wrapper<const T>>
try_get_ref(std::span<const T> s, int idx) {
const int n = static_cast<int>(s.size());
if (idx < 0) {
idx += n;
}
if (idx < 0 || idx >= n) {
return std::nullopt;
}
return std::cref(s[idx]);
}
/**
* @brief a simple RAII helper for `defer` like behavior
*/
struct deferrer {
#ifdef __cpp_lib_move_only_function
using func_t = std::move_only_function<void()>;
#else
using func_t = std::function<void()>;
#endif
template <typename F>
class deferrer {
F f_;
deferrer(func_t &&f) : _f(std::move(f)) {}
public:
explicit constexpr deferrer(F f) : f_(std::move(f)) {}
~deferrer() {
if (_f) {
_f();
}
f_();
}
deferrer(const deferrer &) = delete;
deferrer &operator=(const deferrer &) = delete;
deferrer(deferrer &&other) noexcept : _f(std::move(other._f)) {
other._f = {};
}
deferrer &operator=(deferrer &&other) noexcept {
if (this != &other) {
// call current function before overwriting
if (_f) {
_f();
}
_f = std::move(other._f);
other._f = {};
}
return *this;
}
private:
func_t _f;
deferrer(deferrer &&) = delete;
deferrer &operator=(deferrer &&) = delete;
};
using overflow_error = std::monostate;
/**
* @brief a safe addition that returns an overflow error if the result is out of range
*/
template <typename T>
requires std::is_integral_v<T>
std::expected<T, overflow_error> add_overflow(T a, T b) {
using ue = std::unexpected<overflow_error>;
if constexpr (std::is_unsigned_v<T>) {
// For unsigned: check if a > max - b (rearranged to avoid overflow in the check itself)
if (a > std::numeric_limits<T>::max() - b) {
return ue{overflow_error{}};
}
} else {
// For signed integers: need to check both positive and negative overflow
if (b > 0) {
if (a > std::numeric_limits<T>::max() - b) {
return ue{overflow_error{}};
}
} else {
if (a < std::numeric_limits<T>::min() - b) {
return ue{overflow_error{}};
}
}
}
return a + b;
}
/**
* @see `std::clamp`
*/
template <typename T, T min, T max>
T constrain_value_in_range_static(T value) {
static_assert(min < max, "min must be less than max");
if (value < min) {
return min;
}
if (value > max) {
return max;
}
return value;
}
#ifdef __cpp_lib_format
inline std::string to_mac_string(std::span<const std::byte, 6> mac) {
return std::format("{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",