From 27adcb9f5dcf4f67ae5b48e12b025ba1e12881c8 Mon Sep 17 00:00:00 2001 From: Ben Deane Date: Fri, 1 May 2026 16:24:25 -0600 Subject: [PATCH] :art: Allow conversion when reading a single value Problem: - When reading a register value, it's common to want to get (say) the `std::uint32_t` out directly, rather than having to write: ```c++ read(grp / "reg"_r) | then([] (auto spec) { return spec["reg"_r]; }) ``` Solution: - Allow a template argument to `read` to do the conversion inside. ```c++ read(grp / "reg"_r); ``` --- include/groov/read.hpp | 30 ++++++++++++++++----- test/fail/read/CMakeLists.txt | 5 ++-- test/fail/read/read_multi_conversion.cpp | 34 ++++++++++++++++++++++++ test/read.cpp | 8 ++++++ 4 files changed, 68 insertions(+), 9 deletions(-) create mode 100644 test/fail/read/read_multi_conversion.cpp diff --git a/include/groov/read.hpp b/include/groov/read.hpp index d2589a0..71fd08f 100644 --- a/include/groov/read.hpp +++ b/include/groov/read.hpp @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -84,9 +85,16 @@ consteval auto check_write_only() -> void { L{}, Masks{}); } +template consteval auto check_read_conversion() { + STATIC_ASSERT( + (std::convertible_to), + "Cannot convert the result of read ({}) to {} -- are you reading " + "multiple paths?", + Spec, T); +} } // namespace detail -template +template constexpr auto read(read_spec const &s) -> async::sender auto { using Spec = decltype(to_write_spec(s)); @@ -104,35 +112,43 @@ constexpr auto read(read_spec const &s) -> async::sender auto { detail::check_write_only(); + using R = stdx::conditional_t, Spec, T>; + detail::check_read_conversion(); + return [](stdx::tuple, stdx::tuple) { return async::when_all(detail::read()...) | async::then(stdx::overload{ - [](typename Rs::type_t... values) { + [](typename Rs::type_t... values) -> R { return Spec{{}, {Rs{{}, values}...}}; }, - [](std::optional... values) { + [](std::optional... values) + -> std::optional { return stdx::transform( - [](auto... vs) { return Spec{{}, {Rs{{}, vs}...}}; }, + [](auto... vs) -> R { + return Spec{{}, {Rs{{}, vs}...}}; + }, values...); }}); }(typename Spec::value_t{}, field_masks_t{}); } namespace _read { -struct pipeable { +template struct pipeable { private: template friend constexpr auto operator|(S &&s, pipeable) -> async::sender auto { return std::forward(s) | async::let_value([](Spec &&spec) { - return read(std::forward(spec)); + return read(std::forward(spec)); }); } }; } // namespace _read -constexpr auto read() { return async::compose(_read::pipeable{}); } +template constexpr auto read() { + return async::compose(_read::pipeable{}); +} namespace _sync_read { template [[nodiscard]] auto wait(S &&s) { diff --git a/test/fail/read/CMakeLists.txt b/test/fail/read/CMakeLists.txt index 06e81e1..b72f8fd 100644 --- a/test/fail/read/CMakeLists.txt +++ b/test/fail/read/CMakeLists.txt @@ -10,5 +10,6 @@ if(${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU" AND ${CMAKE_CXX_COMPILER_VERSION} return() endif() -add_fail_tests(read_from_wo_field_indirect_by_register - read_from_wo_field_direct read_from_wo_register_direct) +add_fail_tests( + read_from_wo_field_indirect_by_register read_from_wo_field_direct + read_from_wo_register_direct read_multi_conversion) diff --git a/test/fail/read/read_multi_conversion.cpp b/test/fail/read/read_multi_conversion.cpp new file mode 100644 index 0000000..65aaae3 --- /dev/null +++ b/test/fail/read/read_multi_conversion.cpp @@ -0,0 +1,34 @@ +#include "../dummy_bus.hpp" + +#include +#include +#include +#include + +#include +#include + +#include + +// EXPECT: to int -- are you reading multiple paths + +namespace { +struct read_bus : dummy_bus { + template + static auto read(auto...) -> async::sender auto { + return async::just(42); + } +}; + +using F0 = groov::field<"f0", std::uint8_t, 0, 0>; +using F1 = groov::field<"f1", std::uint8_t, 1, 1>; + +std::uint32_t data{}; +using R = groov::reg<"reg", std::uint32_t, &data, groov::w::replace, F0, F1>; +using G = groov::group<"group", read_bus, R>; +} // namespace + +auto main() -> int { + using namespace groov::literals; + [[maybe_unused]] auto x = read(G{}("reg.f0"_f, "reg.f1"_f)); +} diff --git a/test/read.cpp b/test/read.cpp index 40d3b95..053c829 100644 --- a/test/read.cpp +++ b/test/read.cpp @@ -58,6 +58,14 @@ TEST_CASE("read a register", "[read]") { CHECK(r["reg0"_r] == data0); } +TEST_CASE("read a register and convert", "[read]") { + using namespace groov::literals; + data0 = 0xa5a5'a5a5u; + auto s = read(grp / "reg0"_r); + auto r = get<0>(*(s | async::sync_wait())); + CHECK(r == data0); +} + TEST_CASE("sync_read a register", "[read]") { using namespace groov::literals; data0 = 0xa5a5'a5a5u;