07-litewing-cpp-course-evaluation

Fable evaluation — C++ for LiteWing

BLUF

This is a genuinely good, safety-first course with clean, warning-clean C++20 that compiles and tests green (verified 2026-07-15 on clang 21, and still clean under stricter -Wconversion -Wsign-conversion -Wshadow -Wdouble-promotion). Its standout is the hardware-gate discipline. Its main gap as a modern C++ course is that the code is written in a conservative "C++03-with-std::array" style: it under-uses the four idioms an embedded-C++-2026 course should be built around — constexpr/noexcept, strong types on the hot path (chrono for dt), designated initializers, and value-based error signaling expected/optional. There is also one real testing footgun. Fixes below are prioritized.

Scorecard

Dimension Grade Note
Pedagogical structure & safety gating A Module→lab→boundary-evidence loop and the Module 06 hardware gate are exemplary
Build hygiene A− C++20, extensions off, -Werror; misses a few high-value warnings + sanitizers
Correctness of the code as written A Verified clean compile + passing tests; allocation-free; explicit ctors
Modern C++ idiom adoption C+ The teaching gap — see findings M1–M6
Testing methodology C assert-in-main silently no-ops under -DNDEBUG (proven below)
Honesty / sourcing A Scope, blocked states, and S1–S3 citations are disciplined

What's already right (keep)


Findings & recommendations (prioritized)

M1 — Make the core constexpr + noexcept (highest-value modern gap)

None of clamp, mix_x, LowPass, or Pid are constexpr or noexcept. On ESP-IDF exceptions are off by default, so noexcept is free and honest; and constexpr lets the mixer/filter math fold at compile time and live in ROM. This is the idiom an embedded C++20 course should lead with.

M2 — Resolve the strong-types-vs-naked-float contradiction (dt)

Module 02 preaches "a type prevents treating a time interval as a motor command," but Pid::step(float, float, float dt_seconds) and LowPass::update(float) take naked floats, and the declared ImuRate struct is never used. The course argues its own thesis and then doesn't apply it.

M3 — Signal invalid input instead of silently swallowing it

Two silent behaviors work against the course's stated principles:

M4 — Use designated initializers in the lab

The test builds PidGains positionally: {{2.0F, 1.0F, 0.0F, 0.25F, 0.5F}} — five unlabeled floats, easy to transpose. Module 06 even flags that C++20 designated-initializer rules differ from C, then never uses them.

M5 — Finish the nodiscard story

nodiscard is on LowPass::value() but not on clamp, mix_x, LowPass::update, or Pid::step — all of which return a computed value that must not be dropped. Mark them all.

M6 — Harden the build (cheap, host-only, safe)

M-test — The assert/NDEBUG footgun (fix regardless of the above)

Proven 2026-07-15: compiling the test with -DNDEBUG and a deliberately false assertion still exits 0 — under a release build the entire suite becomes a silent no-op. A course teaching test discipline must not ship a suite that evaporates in release mode.


Suggested "Module 2.5 — Modern C++ for constrained targets"

The cleanest way to absorb M1–M6 pedagogically is one new module between Safe Types (02) and Sensor Pipeline (03):
constexpr/noexcept by default · strong types & std::chrono for physical quantities · designated initializers · value-based error handling with std::expected/optional · compile-time tests with static_assert · toolchain-reality caveat: pin the ESP-IDF GCC version and confirm which C++23 features (notably std::expected) it actually ships before relying on them — default the course to C++20 (as it already does) and mark C++23 items "verify toolchain."

One-line verdict

Excellent safety scaffolding and clean code; to earn the "modern C++" label, make the core constexpr/noexcept, put strong types on the dt path it already preaches, adopt designated initializers and std::expected, and replace the NDEBUG-fragile assert harness.