Skip to content

Commit 6659e8d

Browse files
committed
✨ Add message equivalence
Problem: - equality is not defined for span, so it is also not defined for messages. Solution: - add `msg::equivalent` to determine whether two message instances are equivalent (all fields have the same values). - equivalence is defined for all combinations of owning, const and mutable view types.
1 parent ea3a177 commit 6659e8d

File tree

6 files changed

+211
-6
lines changed

6 files changed

+211
-6
lines changed

docs/message.adoc

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,22 @@ auto data = msg.data();
192192

193193
This always returns a (const-observing) `stdx::span` over the underlying data.
194194

195+
=== Message equivalence
196+
197+
Equality (`operator==`) is not defined on messages. A general definition of
198+
equality is problematic, but that doesn't mean we can't have a useful notion of
199+
equivalence that is spelled differently:
200+
201+
[source,cpp]
202+
----
203+
auto m1 = my_message{"my_field"_field = 42};
204+
auto m2 = my_message{"my_field"_field = 0x2a};
205+
assert(equivalent(m1.as_const_view(), m2.as_mutable_view()));
206+
----
207+
208+
Equivalence means that all fields hold the same values. It is defined for all
209+
combinations of owning messages, const views and mutable views.
210+
195211
=== Handling messages with callbacks
196212

197213
_cib_ contains an implementation of a basic message handler which can be used in

include/msg/message.hpp

Lines changed: 84 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -397,11 +397,40 @@ template <stdx::ct_string Name, typename... Fields> struct message {
397397
[[nodiscard]] constexpr auto data() const { return storage; }
398398

399399
[[nodiscard]] constexpr auto as_owning() { return owner_t{*this}; }
400+
[[nodiscard]] constexpr auto as_const_view() const {
401+
using cv_t =
402+
view_t<stdx::span<std::add_const_t<typename Span::value_type>,
403+
stdx::ct_capacity_v<Span>>>;
404+
return cv_t{*this};
405+
}
400406

401407
private:
402408
static_assert(definition_t::fits_inside<span_t>,
403409
"Fields overflow message storage!");
404410
span_t storage{};
411+
412+
friend constexpr auto operator==(view_t, view_t) -> bool {
413+
static_assert(stdx::always_false_v<Span>,
414+
"Equality is not defined for messages: "
415+
"consider using equivalent() instead.");
416+
return false;
417+
}
418+
419+
using const_span_t =
420+
stdx::span<std::add_const_t<typename Span::value_type>,
421+
stdx::ct_capacity_v<Span>>;
422+
using mutable_span_t =
423+
stdx::span<typename Span::value_type, stdx::ct_capacity_v<Span>>;
424+
425+
friend constexpr auto equiv(view_t lhs,
426+
view_t<const_span_t> rhs) -> bool {
427+
return (... and (lhs.get(Fields{}) == rhs.get(Fields{})));
428+
}
429+
430+
friend constexpr auto equiv(view_t lhs,
431+
view_t<mutable_span_t> rhs) -> bool {
432+
return equiv(lhs, rhs.as_const_view());
433+
}
405434
};
406435
using const_view_t = view_t<default_const_span_t>;
407436
using mutable_view_t = view_t<default_span_t>;
@@ -478,9 +507,27 @@ template <stdx::ct_string Name, typename... Fields> struct message {
478507
"Fields overflow message storage!");
479508
storage_t storage{};
480509

481-
friend constexpr auto operator==(owner_t const &lhs,
482-
owner_t const &rhs) -> bool {
483-
return lhs.storage == rhs.storage;
510+
friend constexpr auto operator==(owner_t const &,
511+
owner_t const &) -> bool {
512+
static_assert(stdx::always_false_v<Storage>,
513+
"Equality is not defined for messages: "
514+
"consider using equivalent() instead.");
515+
return false;
516+
}
517+
518+
using const_span_t = stdx::span<typename Storage::value_type const,
519+
stdx::ct_capacity_v<Storage>>;
520+
using mutable_span_t = stdx::span<typename Storage::value_type,
521+
stdx::ct_capacity_v<Storage>>;
522+
523+
friend constexpr auto equiv(owner_t const &lhs,
524+
view_t<const_span_t> rhs) -> bool {
525+
return (... and (lhs.get(Fields{}) == rhs.get(Fields{})));
526+
}
527+
528+
friend constexpr auto equiv(owner_t const &lhs,
529+
view_t<mutable_span_t> rhs) -> bool {
530+
return equiv(lhs, rhs.as_const_view());
484531
}
485532
};
486533

@@ -518,8 +565,8 @@ template <stdx::ct_string Name, typename... Fields> struct message {
518565

519566
using matcher_t = decltype(match::all(typename Fields::matcher_t{}...));
520567

521-
// NewFields go first because they override existing fields of the same name
522-
// (see unique_by_name)
568+
// NewFields go first because they override existing fields of the same
569+
// name (see unique_by_name)
523570
template <stdx::ct_string NewName, typename... NewFields>
524571
using extension =
525572
message_without_unique_field_names<NewName, NewFields..., Fields...>;
@@ -534,6 +581,16 @@ template <typename T> using owning = typename T::template owner_t<>;
534581
template <typename T> using mutable_view = typename T::mutable_view_t;
535582
template <typename T> using const_view = typename T::const_view_t;
536583

584+
template <typename M>
585+
concept messagelike =
586+
requires { typename std::remove_cvref_t<M>::definition_t; };
587+
template <typename M>
588+
concept viewlike =
589+
messagelike<M> and requires { typename std::remove_cvref_t<M>::span_t; };
590+
template <typename M>
591+
concept owninglike =
592+
messagelike<M> and requires { typename std::remove_cvref_t<M>::storage_t; };
593+
537594
template <typename V, typename Def>
538595
concept view_of = std::same_as<typename V::definition_t, Def> and
539596
Def::template fits_inside<typename V::span_t>;
@@ -543,4 +600,26 @@ concept const_view_of =
543600

544601
template <typename Def, stdx::ct_string Name, typename... Fields>
545602
using extend = typename Def::template extension<Name, Fields...>;
603+
604+
template <owninglike O,
605+
view_of<typename std::remove_cvref_t<O>::definition_t> V>
606+
constexpr auto equivalent(O &&o, V v) -> bool {
607+
return equiv(std::forward<O>(o), v);
608+
}
609+
610+
template <owninglike O,
611+
view_of<typename std::remove_cvref_t<O>::definition_t> V>
612+
constexpr auto equivalent(V v, O &&o) -> bool {
613+
return equiv(std::forward<O>(o), v);
614+
}
615+
616+
template <viewlike V1, view_of<typename V1::definition_t> V2>
617+
constexpr auto equivalent(V1 v1, V2 v2) -> bool {
618+
return equiv(v1, v2);
619+
}
620+
621+
template <owninglike O1, owninglike O2>
622+
constexpr auto equivalent(O1 const &lhs, O2 const &rhs) {
623+
return equiv(lhs, rhs.as_const_view());
624+
}
546625
} // namespace msg

test/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ add_compile_fail_test(msg/fail/owning_msg_incompatible_storage.cpp LIBRARIES
131131
warnings cib)
132132
add_compile_fail_test(msg/fail/owning_msg_incompatible_view.cpp LIBRARIES
133133
warnings cib)
134+
add_compile_fail_test(msg/fail/message_cmp_owner.cpp LIBRARIES warnings cib)
135+
add_compile_fail_test(msg/fail/message_cmp_view.cpp LIBRARIES warnings cib)
134136
add_compile_fail_test(msg/fail/message_const_field_write.cpp LIBRARIES warnings
135137
cib)
136138
add_compile_fail_test(msg/fail/message_dangling_view.cpp LIBRARIES warnings cib)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#include <msg/field.hpp>
2+
#include <msg/message.hpp>
3+
4+
// EXPECT: consider using equivalent
5+
namespace {
6+
using namespace msg;
7+
8+
using test_field1 =
9+
field<"f1", std::uint32_t>::located<at{0_dw, 31_msb, 24_lsb}>;
10+
using test_field2 = field<"f2", std::uint32_t>::located<at{1_dw, 7_msb, 0_lsb}>;
11+
12+
using msg_defn = message<"test_msg", test_field1, test_field2>;
13+
} // namespace
14+
15+
auto main() -> int {
16+
owning<msg_defn> m1{};
17+
auto m2 = m1;
18+
[[maybe_unused]] auto b = m1 == m2;
19+
}

test/msg/fail/message_cmp_view.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#include <msg/field.hpp>
2+
#include <msg/message.hpp>
3+
4+
// EXPECT: consider using equivalent
5+
namespace {
6+
using namespace msg;
7+
8+
using test_field1 =
9+
field<"f1", std::uint32_t>::located<at{0_dw, 31_msb, 24_lsb}>;
10+
using test_field2 = field<"f2", std::uint32_t>::located<at{1_dw, 7_msb, 0_lsb}>;
11+
12+
using msg_defn = message<"test_msg", test_field1, test_field2>;
13+
} // namespace
14+
15+
auto main() -> int {
16+
owning<msg_defn> m{};
17+
auto mv1 = m.as_mutable_view();
18+
auto mv2 = m.as_mutable_view();
19+
[[maybe_unused]] auto b = mv1 == mv2;
20+
}

test/msg/message.cpp

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@
33

44
#include <catch2/catch_test_macros.hpp>
55

6+
#include <algorithm>
7+
#include <array>
8+
#include <cstdint>
9+
#include <functional>
10+
#include <iterator>
11+
#include <string>
12+
#include <type_traits>
13+
614
namespace {
715
using namespace msg;
816

@@ -212,7 +220,18 @@ TEST_CASE("owning from view", "[message]") {
212220
auto v = msg.as_mutable_view();
213221
auto o = v.as_owning();
214222
static_assert(std::is_same_v<decltype(o), test_msg>);
215-
CHECK(msg == o);
223+
CHECK(std::equal(msg.data().begin(), msg.data().end(), o.data().begin()));
224+
}
225+
226+
TEST_CASE("const view from mutable view", "[message]") {
227+
test_msg msg{};
228+
auto mv = msg.as_mutable_view();
229+
auto v = mv.as_const_view();
230+
static_assert(
231+
std::is_same_v<decltype(v.data()),
232+
typename test_msg::definition_t::default_const_span_t>);
233+
msg.set("f1"_field = 0xba11);
234+
CHECK(0xba11 == v.get("f1"_field));
216235
}
217236

218237
TEST_CASE("equal_to matcher", "[message]") {
@@ -431,3 +450,53 @@ TEST_CASE("extend message with new field constraint", "[message]") {
431450
using expected_defn = message<"msg", id_field, field1::with_required<1>>;
432451
static_assert(std::is_same_v<defn, expected_defn>);
433452
}
453+
454+
TEST_CASE("message equivalence (owning)", "[message]") {
455+
test_msg m1{"f1"_field = 0xba11, "f2"_field = 0x42, "f3"_field = 0xd00d};
456+
test_msg m2{"f1"_field = 0xba11, "f2"_field = 0x42, "f3"_field = 0xd00d};
457+
CHECK(equivalent(m1, m2));
458+
test_msg other{"f1"_field = 0xba11, "f2"_field = 0x42, "f3"_field = 0xd00f};
459+
CHECK(not equivalent(m1, other));
460+
}
461+
462+
TEST_CASE("message equivalence (owning/const_view)", "[message]") {
463+
owning<msg_defn> m{"f1"_field = 0xba11, "f2"_field = 0x42,
464+
"f3"_field = 0xd00d};
465+
auto cv = m.as_const_view();
466+
CHECK(equivalent(m, cv));
467+
CHECK(equivalent(cv, m));
468+
469+
owning<msg_defn> other{"f1"_field = 0xba11, "f2"_field = 0x42,
470+
"f3"_field = 0xd00f};
471+
CHECK(not equivalent(other, cv));
472+
CHECK(not equivalent(cv, other));
473+
}
474+
475+
TEST_CASE("message equivalence (owning/mutable_view)", "[message]") {
476+
owning<msg_defn> m{"f1"_field = 0xba11, "f2"_field = 0x42,
477+
"f3"_field = 0xd00d};
478+
auto mv = m.as_mutable_view();
479+
CHECK(equivalent(m, mv));
480+
CHECK(equivalent(mv, m));
481+
482+
owning<msg_defn> other{"f1"_field = 0xba11, "f2"_field = 0x42,
483+
"f3"_field = 0xd00f};
484+
CHECK(not equivalent(other, mv));
485+
CHECK(not equivalent(mv, other));
486+
}
487+
488+
TEST_CASE("message equivalence (views)", "[message]") {
489+
owning<msg_defn> m{"f1"_field = 0xba11, "f2"_field = 0x42,
490+
"f3"_field = 0xd00d};
491+
auto cv1 = m.as_const_view();
492+
auto mv1 = m.as_mutable_view();
493+
CHECK(equivalent(cv1, cv1));
494+
CHECK(equivalent(mv1, mv1));
495+
CHECK(equivalent(cv1, mv1));
496+
CHECK(equivalent(mv1, cv1));
497+
498+
owning<msg_defn> other{"f1"_field = 0xba11, "f2"_field = 0x42,
499+
"f3"_field = 0xd00f};
500+
auto cv2 = other.as_const_view();
501+
CHECK(not equivalent(cv1, cv2));
502+
}

0 commit comments

Comments
 (0)