Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions docs/message.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,22 @@ auto data = msg.data();

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

=== Message equivalence

Equality (`operator==`) is not defined on messages. A general definition of
equality is problematic, but that doesn't mean we can't have a useful notion of
equivalence that is spelled differently:

[source,cpp]
----
auto m1 = my_message{"my_field"_field = 42};
auto m2 = my_message{"my_field"_field = 0x2a};
assert(equivalent(m1.as_const_view(), m2.as_mutable_view()));
----

Equivalence means that all fields hold the same values. It is defined for all
combinations of owning messages, const views and mutable views.

=== Handling messages with callbacks

_cib_ contains an implementation of a basic message handler which can be used in
Expand Down
89 changes: 84 additions & 5 deletions include/msg/message.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -397,11 +397,40 @@ template <stdx::ct_string Name, typename... Fields> struct message {
[[nodiscard]] constexpr auto data() const { return storage; }

[[nodiscard]] constexpr auto as_owning() { return owner_t{*this}; }
[[nodiscard]] constexpr auto as_const_view() const {
using cv_t =
view_t<stdx::span<std::add_const_t<typename Span::value_type>,
stdx::ct_capacity_v<Span>>>;
return cv_t{*this};
}

private:
static_assert(definition_t::fits_inside<span_t>,
"Fields overflow message storage!");
span_t storage{};

friend constexpr auto operator==(view_t, view_t) -> bool {
static_assert(stdx::always_false_v<Span>,
"Equality is not defined for messages: "
"consider using equivalent() instead.");
return false;
}

using const_span_t =
stdx::span<std::add_const_t<typename Span::value_type>,
stdx::ct_capacity_v<Span>>;
using mutable_span_t =
stdx::span<typename Span::value_type, stdx::ct_capacity_v<Span>>;

friend constexpr auto equiv(view_t lhs,
view_t<const_span_t> rhs) -> bool {
return (... and (lhs.get(Fields{}) == rhs.get(Fields{})));
}

friend constexpr auto equiv(view_t lhs,
view_t<mutable_span_t> rhs) -> bool {
return equiv(lhs, rhs.as_const_view());
}
};
using const_view_t = view_t<default_const_span_t>;
using mutable_view_t = view_t<default_span_t>;
Expand Down Expand Up @@ -478,9 +507,27 @@ template <stdx::ct_string Name, typename... Fields> struct message {
"Fields overflow message storage!");
storage_t storage{};

friend constexpr auto operator==(owner_t const &lhs,
owner_t const &rhs) -> bool {
return lhs.storage == rhs.storage;
friend constexpr auto operator==(owner_t const &,
owner_t const &) -> bool {
static_assert(stdx::always_false_v<Storage>,
"Equality is not defined for messages: "
"consider using equivalent() instead.");
return false;
}

using const_span_t = stdx::span<typename Storage::value_type const,
stdx::ct_capacity_v<Storage>>;
using mutable_span_t = stdx::span<typename Storage::value_type,
stdx::ct_capacity_v<Storage>>;

friend constexpr auto equiv(owner_t const &lhs,
view_t<const_span_t> rhs) -> bool {
return (... and (lhs.get(Fields{}) == rhs.get(Fields{})));
}

friend constexpr auto equiv(owner_t const &lhs,
view_t<mutable_span_t> rhs) -> bool {
return equiv(lhs, rhs.as_const_view());
}
};

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

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

// NewFields go first because they override existing fields of the same name
// (see unique_by_name)
// NewFields go first because they override existing fields of the same
// name (see unique_by_name)
template <stdx::ct_string NewName, typename... NewFields>
using extension =
message_without_unique_field_names<NewName, NewFields..., Fields...>;
Expand All @@ -534,6 +581,16 @@ template <typename T> using owning = typename T::template owner_t<>;
template <typename T> using mutable_view = typename T::mutable_view_t;
template <typename T> using const_view = typename T::const_view_t;

template <typename M>
concept messagelike =
requires { typename std::remove_cvref_t<M>::definition_t; };
template <typename M>
concept viewlike =
messagelike<M> and requires { typename std::remove_cvref_t<M>::span_t; };
template <typename M>
concept owninglike =
messagelike<M> and requires { typename std::remove_cvref_t<M>::storage_t; };

template <typename V, typename Def>
concept view_of = std::same_as<typename V::definition_t, Def> and
Def::template fits_inside<typename V::span_t>;
Expand All @@ -543,4 +600,26 @@ concept const_view_of =

template <typename Def, stdx::ct_string Name, typename... Fields>
using extend = typename Def::template extension<Name, Fields...>;

template <owninglike O,
view_of<typename std::remove_cvref_t<O>::definition_t> V>
constexpr auto equivalent(O &&o, V v) -> bool {
return equiv(std::forward<O>(o), v);
}

template <owninglike O,
view_of<typename std::remove_cvref_t<O>::definition_t> V>
constexpr auto equivalent(V v, O &&o) -> bool {
return equiv(std::forward<O>(o), v);
}

template <viewlike V1, view_of<typename V1::definition_t> V2>
constexpr auto equivalent(V1 v1, V2 v2) -> bool {
return equiv(v1, v2);
}

template <owninglike O1, owninglike O2>
constexpr auto equivalent(O1 const &lhs, O2 const &rhs) {
return equiv(lhs, rhs.as_const_view());
}
} // namespace msg
2 changes: 2 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ add_compile_fail_test(msg/fail/owning_msg_incompatible_storage.cpp LIBRARIES
warnings cib)
add_compile_fail_test(msg/fail/owning_msg_incompatible_view.cpp LIBRARIES
warnings cib)
add_compile_fail_test(msg/fail/message_cmp_owner.cpp LIBRARIES warnings cib)
add_compile_fail_test(msg/fail/message_cmp_view.cpp LIBRARIES warnings cib)
add_compile_fail_test(msg/fail/message_const_field_write.cpp LIBRARIES warnings
cib)
add_compile_fail_test(msg/fail/message_dangling_view.cpp LIBRARIES warnings cib)
Expand Down
19 changes: 19 additions & 0 deletions test/msg/fail/message_cmp_owner.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#include <msg/field.hpp>
#include <msg/message.hpp>

// EXPECT: consider using equivalent
namespace {
using namespace msg;

using test_field1 =
field<"f1", std::uint32_t>::located<at{0_dw, 31_msb, 24_lsb}>;
using test_field2 = field<"f2", std::uint32_t>::located<at{1_dw, 7_msb, 0_lsb}>;

using msg_defn = message<"test_msg", test_field1, test_field2>;
} // namespace

auto main() -> int {
owning<msg_defn> m1{};
auto m2 = m1;
[[maybe_unused]] auto b = m1 == m2;
}
20 changes: 20 additions & 0 deletions test/msg/fail/message_cmp_view.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#include <msg/field.hpp>
#include <msg/message.hpp>

// EXPECT: consider using equivalent
namespace {
using namespace msg;

using test_field1 =
field<"f1", std::uint32_t>::located<at{0_dw, 31_msb, 24_lsb}>;
using test_field2 = field<"f2", std::uint32_t>::located<at{1_dw, 7_msb, 0_lsb}>;

using msg_defn = message<"test_msg", test_field1, test_field2>;
} // namespace

auto main() -> int {
owning<msg_defn> m{};
auto mv1 = m.as_mutable_view();
auto mv2 = m.as_mutable_view();
[[maybe_unused]] auto b = mv1 == mv2;
}
71 changes: 70 additions & 1 deletion test/msg/message.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@

#include <catch2/catch_test_macros.hpp>

#include <algorithm>
#include <array>
#include <cstdint>
#include <functional>
#include <iterator>
#include <string>
#include <type_traits>

namespace {
using namespace msg;

Expand Down Expand Up @@ -212,7 +220,18 @@ TEST_CASE("owning from view", "[message]") {
auto v = msg.as_mutable_view();
auto o = v.as_owning();
static_assert(std::is_same_v<decltype(o), test_msg>);
CHECK(msg == o);
CHECK(std::equal(msg.data().begin(), msg.data().end(), o.data().begin()));
}

TEST_CASE("const view from mutable view", "[message]") {
test_msg msg{};
auto mv = msg.as_mutable_view();
auto v = mv.as_const_view();
static_assert(
std::is_same_v<decltype(v.data()),
typename test_msg::definition_t::default_const_span_t>);
msg.set("f1"_field = 0xba11);
CHECK(0xba11 == v.get("f1"_field));
}

TEST_CASE("equal_to matcher", "[message]") {
Expand Down Expand Up @@ -431,3 +450,53 @@ TEST_CASE("extend message with new field constraint", "[message]") {
using expected_defn = message<"msg", id_field, field1::with_required<1>>;
static_assert(std::is_same_v<defn, expected_defn>);
}

TEST_CASE("message equivalence (owning)", "[message]") {
test_msg m1{"f1"_field = 0xba11, "f2"_field = 0x42, "f3"_field = 0xd00d};
test_msg m2{"f1"_field = 0xba11, "f2"_field = 0x42, "f3"_field = 0xd00d};
CHECK(equivalent(m1, m2));
test_msg other{"f1"_field = 0xba11, "f2"_field = 0x42, "f3"_field = 0xd00f};
CHECK(not equivalent(m1, other));
}

TEST_CASE("message equivalence (owning/const_view)", "[message]") {
owning<msg_defn> m{"f1"_field = 0xba11, "f2"_field = 0x42,
"f3"_field = 0xd00d};
auto cv = m.as_const_view();
CHECK(equivalent(m, cv));
CHECK(equivalent(cv, m));

owning<msg_defn> other{"f1"_field = 0xba11, "f2"_field = 0x42,
"f3"_field = 0xd00f};
CHECK(not equivalent(other, cv));
CHECK(not equivalent(cv, other));
}

TEST_CASE("message equivalence (owning/mutable_view)", "[message]") {
owning<msg_defn> m{"f1"_field = 0xba11, "f2"_field = 0x42,
"f3"_field = 0xd00d};
auto mv = m.as_mutable_view();
CHECK(equivalent(m, mv));
CHECK(equivalent(mv, m));

owning<msg_defn> other{"f1"_field = 0xba11, "f2"_field = 0x42,
"f3"_field = 0xd00f};
CHECK(not equivalent(other, mv));
CHECK(not equivalent(mv, other));
}

TEST_CASE("message equivalence (views)", "[message]") {
owning<msg_defn> m{"f1"_field = 0xba11, "f2"_field = 0x42,
"f3"_field = 0xd00d};
auto cv1 = m.as_const_view();
auto mv1 = m.as_mutable_view();
CHECK(equivalent(cv1, cv1));
CHECK(equivalent(mv1, mv1));
CHECK(equivalent(cv1, mv1));
CHECK(equivalent(mv1, cv1));

owning<msg_defn> other{"f1"_field = 0xba11, "f2"_field = 0x42,
"f3"_field = 0xd00f};
auto cv2 = other.as_const_view();
CHECK(not equivalent(cv1, cv2));
}