Skip to content

Commit db8d4ce

Browse files
committed
Add array comparison operators
1 parent 6cb99b3 commit db8d4ce

File tree

4 files changed

+125
-27
lines changed

4 files changed

+125
-27
lines changed

containers/array.h

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "marker/unsafe.h"
2626
#include "mem/move.h"
2727
#include "mem/relocate.h"
28+
#include "num/num_concepts.h"
2829

2930
namespace sus::containers {
3031

@@ -91,9 +92,7 @@ class Array final {
9192
}
9293

9394
/// Returns the number of elements in the array.
94-
constexpr const /* TODO: usize */ size_t len() const& noexcept {
95-
return N;
96-
}
95+
constexpr const /* TODO: usize */ size_t len() const& noexcept { return N; }
9796

9897
constexpr const T& get(/* TODO: usize */ size_t i) const& noexcept
9998
requires(N > 0)
@@ -123,7 +122,11 @@ class Array final {
123122
return storage_.data_;
124123
}
125124

126-
// TODO: Eq and Ord (like Option and Tuple).
125+
/// sus::num::Eq<Array<U, N>> trait.
126+
template <::sus::num::Eq<T> U>
127+
constexpr bool operator==(const Array<U, N>& r) const& noexcept {
128+
return eq_impl(r, std::make_index_sequence<N>());
129+
}
127130

128131
private:
129132
enum WithInitializer { kWithInitializer };
@@ -150,6 +153,12 @@ class Array final {
150153
new (a + index) T(move(t1));
151154
}
152155

156+
template <class U, size_t... Is>
157+
constexpr inline auto eq_impl(const Array<U, N>& r,
158+
std::index_sequence<Is...>) const& noexcept {
159+
return (true && ... && (get(Is) == r.get(Is)));
160+
};
161+
153162
// Using a union ensures that the default constructor doesn't initialize
154163
// anything.
155164
union {
@@ -160,6 +169,56 @@ class Array final {
160169
::sus::mem::relocate_array_by_memcpy<T>);
161170
};
162171

172+
namespace __private {
173+
174+
template <size_t I, class O, class T, class U, size_t N>
175+
constexpr inline bool array_cmp_impl(O& val, const Array<T, N>& l,
176+
const Array<U, N>& r) noexcept {
177+
auto cmp = l.get(I) <=> r.get(I);
178+
// Allow downgrading from equal to equivalent, but not the inverse.
179+
if (cmp != 0) val = cmp;
180+
// Short circuit by returning true when we find a difference.
181+
return val == 0;
182+
};
183+
184+
template <class T, class U, size_t N, size_t... Is>
185+
constexpr inline auto array_cmp(auto equal, const Array<T, N>& l,
186+
const Array<U, N>& r,
187+
std::index_sequence<Is...>) noexcept {
188+
auto val = equal;
189+
(true && ... && (array_cmp_impl<Is>(val, l, r)));
190+
return val;
191+
};
192+
193+
} // namespace __private
194+
195+
/// sus::num::Ord<Option<U>> trait.
196+
template <class T, class U, size_t N>
197+
requires(::sus::num::ExclusiveOrd<T, U>)
198+
constexpr inline auto operator<=>(const Array<T, N>& l,
199+
const Array<U, N>& r) noexcept {
200+
return __private::array_cmp(std::strong_ordering::equivalent, l, r,
201+
std::make_index_sequence<N>());
202+
}
203+
204+
/// sus::num::Ord<Option<U>> trait.
205+
template <class T, class U, size_t N>
206+
requires(::sus::num::ExclusiveWeakOrd<T, U>)
207+
constexpr inline auto operator<=>(const Array<T, N>& l,
208+
const Array<U, N>& r) noexcept {
209+
return __private::array_cmp(std::weak_ordering::equivalent, l, r,
210+
std::make_index_sequence<N>());
211+
}
212+
213+
/// sus::num::Ord<Option<U>> trait.
214+
template <class T, class U, size_t N>
215+
requires(::sus::num::ExclusivePartialOrd<T, U>)
216+
constexpr inline auto operator<=>(const Array<T, N>& l,
217+
const Array<U, N>& r) noexcept {
218+
return __private::array_cmp(std::partial_ordering::equivalent, l, r,
219+
std::make_index_sequence<N>());
220+
}
221+
163222
} // namespace sus::containers
164223

165224
namespace sus {

containers/array_unittest.cc

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,4 +197,58 @@ TEST(Array, AsPtrMut) {
197197
EXPECT_EQ(101, *(r + 2));
198198
}
199199

200+
TEST(Array, Eq) {
201+
auto a = Array<int, 5>::with_initializer([i = 0]() mutable { return ++i; });
202+
auto b = Array<int, 5>::with_initializer([i = 0]() mutable { return ++i; });
203+
EXPECT_EQ(a, b);
204+
b.get_mut(3) += 1;
205+
EXPECT_NE(a, b);
206+
}
207+
208+
TEST(Array, Ord) {
209+
auto a = Array<int, 5>::with_initializer([i = 0]() mutable { return ++i; });
210+
auto b = Array<int, 5>::with_initializer([i = 0]() mutable { return ++i; });
211+
EXPECT_LE(a, b);
212+
EXPECT_GE(a, b);
213+
b.get_mut(3) += 1;
214+
EXPECT_LT(a, b);
215+
}
216+
217+
TEST(Array, StrongOrder) {
218+
auto a = Array<int, 5>::with_initializer([i = 0]() mutable { return ++i; });
219+
auto b = Array<int, 5>::with_initializer([i = 0]() mutable { return ++i; });
220+
EXPECT_EQ(std::strong_order(a, b), std::strong_ordering::equal);
221+
b.get_mut(3) += 1;
222+
EXPECT_EQ(std::strong_order(a, b), std::strong_ordering::less);
223+
}
224+
225+
struct Weak final {
226+
auto operator==(Weak const& o) const& { return a == o.a && b == o.b; }
227+
auto operator<=>(Weak const& o) const& {
228+
if (a == o.a) return std::weak_ordering::equivalent;
229+
if (a < o.a) return std::weak_ordering::less;
230+
return std::weak_ordering::greater;
231+
}
232+
233+
Weak(int a, int b) : a(a), b(b) {}
234+
int a;
235+
int b;
236+
};
237+
238+
TEST(Array, WeakOrder) {
239+
auto a = Array<Weak, 5>::with_initializer([i = 0]() mutable { return Weak(++i, 2); });
240+
auto b = Array<Weak, 5>::with_initializer([i = 0]() mutable { return Weak(++i, 2); });
241+
EXPECT_EQ(std::weak_order(a, b), std::weak_ordering::equivalent);
242+
b.get_mut(3).a += 1;
243+
EXPECT_EQ(std::weak_order(a, b), std::weak_ordering::less);
244+
}
245+
246+
TEST(Array, PartialOrder) {
247+
auto a = Array<float, 5>::with_initializer([i = 0.f]() mutable { return ++i; });
248+
auto b = Array<float, 5>::with_initializer([i = 0.f]() mutable { return ++i; });
249+
EXPECT_EQ(std::partial_order(a, b), std::partial_ordering::equivalent);
250+
b.get_mut(3) += 1;
251+
EXPECT_EQ(std::partial_order(a, b), std::partial_ordering::less);
252+
}
253+
200254
} // namespace

num/f32_unittest.cc

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -980,24 +980,14 @@ TEST(f32, ToBeBytes) {
980980
auto array = (12.5_f32).to_be_bytes();
981981
auto expected =
982982
sus::Array<u8, 4>::with_values(0x41_u8, 0x48_u8, 0x00_u8, 0x00_u8);
983-
// TODO: Use Array::operator== via EXPECT_EQ.
984-
EXPECT_EQ(array.len(), expected.len());
985-
for (size_t i = 0; i < array.len(); i += 1) {
986-
SCOPED_TRACE(i);
987-
EXPECT_EQ(array.get(i), expected.get(i));
988-
}
983+
EXPECT_EQ(array, expected);
989984
}
990985

991986
TEST(f32, ToLeBytes) {
992987
auto array = (12.5_f32).to_le_bytes();
993988
auto expected =
994989
sus::Array<u8, 4>::with_values(0x00_u8, 0x00_u8, 0x48_u8, 0x41_u8);
995-
// TODO: Use Array::operator== via EXPECT_EQ.
996-
EXPECT_EQ(array.len(), expected.len());
997-
for (size_t i = 0; i < array.len(); i += 1) {
998-
SCOPED_TRACE(i);
999-
EXPECT_EQ(array.get(i), expected.get(i));
1000-
}
990+
EXPECT_EQ(array, expected);
1001991
}
1002992

1003993
TEST(f32, ToNeBytes) {
@@ -1006,12 +996,7 @@ TEST(f32, ToNeBytes) {
1006996
sus::assertions::is_big_endian()
1007997
? sus::Array<u8, 4>::with_values(0x41_u8, 0x48_u8, 0x00_u8, 0x00_u8)
1008998
: sus::Array<u8, 4>::with_values(0x00_u8, 0x00_u8, 0x48_u8, 0x41_u8);
1009-
// TODO: Use Array::operator== via EXPECT_EQ.
1010-
EXPECT_EQ(array.len(), expected.len());
1011-
for (size_t i = 0; i < array.len(); i += 1) {
1012-
SCOPED_TRACE(i);
1013-
EXPECT_EQ(array.get(i), expected.get(i));
1014-
}
999+
EXPECT_EQ(array, expected);
10151000
}
10161001

10171002
TEST(f32, FromBeBytes) {

num/num_concepts.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -153,15 +153,15 @@ concept PartialOrd = WeakOrd<Lhs, Rhs> || Ord<Lhs, Rhs> ||
153153
template <class Lhs, class Rhs>
154154
concept ExclusiveOrd = Ord<Lhs, Rhs>;
155155
template <class Lhs, class Rhs>
156-
concept ExclusiveWeakOrd = !
157-
Ord<Lhs, Rhs>&& WeakOrd<Lhs, Rhs>;
156+
concept ExclusiveWeakOrd = (!Ord<Lhs, Rhs> && WeakOrd<Lhs, Rhs>);
158157
template <class Lhs, class Rhs>
159-
concept ExclusivePartialOrd = !
160-
Ord<Lhs, Rhs> && !WeakOrd<Lhs, Rhs> && PartialOrd<Lhs, Rhs>;
158+
concept ExclusivePartialOrd = (!Ord<Lhs, Rhs> && !WeakOrd<Lhs, Rhs> &&
159+
PartialOrd<Lhs, Rhs>);
161160

162161
// TODO: Move this out of ::num.
163162
//
164-
// TODO: How do we do PartialEq? Can we even? Can we require Ord to be Eq? But then it depends on ::num?
163+
// TODO: How do we do PartialEq? Can we even? Can we require Ord to be Eq? But
164+
// then it depends on ::num?
165165
template <class Lhs, class Rhs>
166166
concept Eq = requires(const Lhs& lhs, const Rhs& rhs) {
167167
{ lhs == rhs } -> std::same_as<bool>;

0 commit comments

Comments
 (0)