Repository: bemanproject/inplace_vector
Pull request #71: Refactor threeway compare operator
Author: login: 20162026
Created: 2025-05-06T15:38:26Z | Merged: 2025-05-10T15:58:01Z

reimplement `<=>` as the previous implementation was questionable and failed in some cases.

based on rules described at: https://en.cppreference.com/w/cpp/container/inplace_vector/operator_cmp


Diff:
diff --git a/include/beman/inplace_vector/inplace_vector.hpp b/include/beman/inplace_vector/inplace_vector.hpp
index 320cfd1..8153afb 100644
--- a/include/beman/inplace_vector/inplace_vector.hpp
+++ b/include/beman/inplace_vector/inplace_vector.hpp
@@ -254,6 +254,7 @@ Software.
  */
 #include <algorithm> // for rotate...
 #include <array>
+#include <compare>
 #include <concepts>   // for lots...
 #include <cstddef>    // for size_t
 #include <cstdint>    // for fixed-width integer types
@@ -309,6 +310,11 @@ concept container_compatible_range =
 template <typename T, std::size_t N>
 concept satify_constexpr = N == 0 || std::is_trivial_v<T>;
 
+template <typename T>
+concept lessthan_comparable = requires(const T &a, const T &b) {
+  { a < b } -> std::convertible_to<bool>;
+};
+
 } // namespace beman::details::inplace_vector
 
 // Types implementing the `inplace_vector`'s storage
@@ -1001,27 +1007,25 @@ struct inplace_vector
     insert_range(begin(), il);
   }
 
-  constexpr friend int /*synth-three-way-result<T>*/
-  operator<=>(const inplace_vector & x, const inplace_vector & y) {
-    if (x.size() < y.size())
-      return -1;
-    if (x.size() > y.size())
-      return +1;
-
-    bool all_equal = true;
-    bool all_less = true;
-    for (size_type i = 0; i < x.size(); ++i) {
-      if (x[i] < y[i])
-        all_equal = false;
-      if (x[i] == y[i])
-        all_less = false;
+  constexpr friend auto operator<=>(const inplace_vector &x,
+                                    const inplace_vector &y)
+    requires(beman::details::inplace_vector::lessthan_comparable<T>)
+  {
+    if constexpr (std::three_way_comparable<T>) {
+      return std::lexicographical_compare_three_way(x.begin(), x.end(),
+                                                    y.begin(), y.end());
+    } else {
+      const auto sz = std::min(x.size(), y.size());
+      for (std::size_t i = 0; i < sz; ++i) {
+        if (x[i] < y[i])
+          return std::strong_ordering::less;
+        if (y[i] < x[i])
+          return std::strong_ordering::greater;
+        // [container.opt.reqmts] < must be total ordering relationship
+      }
+
+      return x.size() <=> y.size();
     }
-
-    if (all_equal)
-      return 0;
-    if (all_less)
-      return -1;
-    return 1;
   }
 };
 
diff --git a/tests/beman/inplace_vector/CMakeLists.txt b/tests/beman/inplace_vector/CMakeLists.txt
index e0a01b4..a746209 100644
--- a/tests/beman/inplace_vector/CMakeLists.txt
+++ b/tests/beman/inplace_vector/CMakeLists.txt
@@ -37,6 +37,7 @@ endfunction()
 # Tests for official specs
 add_gtest(container_requirements)
 add_gtest(triviality)
+add_gtest(compare)
 add_gtest(constructors)
 add_gtest(size_n_data)
 add_gtest(erasure)
diff --git a/tests/beman/inplace_vector/compare.test.cpp b/tests/beman/inplace_vector/compare.test.cpp
new file mode 100644
index 0000000..cdaa458
--- /dev/null
+++ b/tests/beman/inplace_vector/compare.test.cpp
@@ -0,0 +1,326 @@
+#include <beman/inplace_vector/inplace_vector.hpp>
+#include <gtest/gtest.h>
+
+#include <cmath>
+#include <compare>
+
+using namespace beman;
+
+template <class T>
+concept has_threeway = requires(const T &t) {
+  { t <=> t };
+};
+
+template <class T>
+concept lessthan_comparable =
+    beman::details::inplace_vector::lessthan_comparable<T>;
+
+template <typename T> struct vec_list {
+  T empty;
+  T base;            // base
+  T copy;            // identical to base
+  T greater;         // greater number of elements
+  T lesser;          // lesser number of elements
+  T bigger;          // bigger value of the elements
+  T smaller;         // smaller value of the elements
+  T greater_smaller; // greater number of elements but smaller values
+  T lesser_bigger;   // lesser number of elements but bigger values
+};
+
+template <typename T> static void runtests(vec_list<T> &list) {
+
+  static_assert(std::three_way_comparable<T> || lessthan_comparable<T>);
+
+  // if T::value_type is threewaycomparable with ordering X then T must also
+  // be comparable with ordering X
+
+  using VT = typename T::value_type;
+
+  if constexpr (std::three_way_comparable<VT, std::strong_ordering>)
+    static_assert(std::three_way_comparable<T, std::strong_ordering>);
+
+  if constexpr (std::three_way_comparable<VT, std::weak_ordering>)
+    static_assert(std::three_way_comparable<T, std::weak_ordering>);
+
+  if constexpr (std::three_way_comparable<VT, std::partial_ordering>)
+    static_assert(std::three_way_comparable<T, std::partial_ordering>);
+
+  if constexpr (std::equality_comparable<VT>) {
+    EXPECT_TRUE(list.empty == list.empty);
+    EXPECT_TRUE(list.empty != list.base);
+  }
+
+  EXPECT_TRUE((list.base <=> list.copy) == 0);
+  EXPECT_TRUE((list.base <=> list.greater) < 0);
+  EXPECT_TRUE((list.base <=> list.lesser) > 0);
+
+  EXPECT_TRUE((list.base <=> list.bigger) < 0);
+  EXPECT_TRUE((list.base <=> list.smaller) > 0);
+
+  EXPECT_TRUE((list.base <=> list.greater_smaller) < 0);
+  EXPECT_TRUE((list.base <=> list.lesser_bigger) > 0);
+
+  if constexpr (std::equality_comparable<VT>) {
+    EXPECT_TRUE(list.base == list.copy);
+    EXPECT_TRUE(list.base != list.greater);
+    EXPECT_TRUE(list.base != list.lesser);
+  }
+  EXPECT_TRUE(list.base <= list.copy);
+  EXPECT_TRUE(list.base >= list.copy);
+  EXPECT_TRUE(list.base < list.greater);
+  EXPECT_TRUE(list.base <= list.greater);
+  EXPECT_TRUE(list.base > list.lesser);
+  EXPECT_TRUE(list.base >= list.lesser);
+
+  if constexpr (std::equality_comparable<VT>) {
+    EXPECT_TRUE(list.copy == list.base);
+    EXPECT_TRUE(list.copy != list.greater);
+    EXPECT_TRUE(list.copy != list.lesser);
+  }
+  EXPECT_TRUE(list.copy <= list.base);
+  EXPECT_TRUE(list.copy >= list.base);
+  EXPECT_TRUE(list.greater > list.base);
+  EXPECT_TRUE(list.greater >= list.base);
+  EXPECT_TRUE(list.lesser < list.base);
+  EXPECT_TRUE(list.lesser <= list.base);
+};
+
+TEST(Compare, threeway_int) {
+  vec_list<inplace_vector<int, 4>> list{
+      .empty{},
+      .base{1, 2, 3},
+      .copy{1, 2, 3},
+      .greater{4, 5, 6},
+      .lesser{0, 0, 0},
+      .bigger{1, 2, 3, 0},
+      .smaller{1, 2},
+      .greater_smaller{2, 2},
+      .lesser_bigger{0, 2, 3, 4},
+  };
+
+  runtests(list);
+}
+
+TEST(Compare, threeway_float) {
+  vec_list<inplace_vector<float, 4>> list{
+      .empty{},
+      .base{1.0f, 2.0f, 3.0f},
+      .copy{1.0f, 2.0f, 3.0f},
+      .greater{4.0f, 5.0f, 6.0f},
+      .lesser{0.0f, 0.0f, 0.0f},
+      .bigger{1.0f, 2.0f, 3.0f, 0.0f},
+      .smaller{1.0f, 2.0f},
+      .greater_smaller{2.0f, 2.0f},
+      .lesser_bigger{0.0f, 2.0f, 3.0f, 4.0f},
+  };
+
+  runtests(list);
+
+  // compare unorderable values
+
+  EXPECT_EQ(std::nanf("") <=> std::nanf(""), std::partial_ordering::unordered);
+  EXPECT_FALSE(std::nanf("") == std::nanf(""));
+  EXPECT_FALSE(std::nanf("") < std::nanf(""));
+  EXPECT_FALSE(std::nanf("") > std::nanf(""));
+  EXPECT_FALSE(std::nanf("") >= std::nanf(""));
+  EXPECT_FALSE(std::nanf("") <= std::nanf(""));
+
+  inplace_vector<float, 4> vnan{std::nanf("")};
+  inplace_vector<float, 4> vnan2{std::nanf("")};
+
+  EXPECT_EQ(vnan <=> vnan2, std::partial_ordering::unordered);
+  EXPECT_FALSE(vnan == vnan2);
+  EXPECT_FALSE(vnan < vnan2);
+  EXPECT_FALSE(vnan > vnan2);
+  EXPECT_FALSE(vnan >= vnan2);
+  EXPECT_FALSE(vnan <= vnan2);
+}
+
+TEST(Compare, threeway_comparable1) {
+  struct comparable1 {
+    int a;
+    int b;
+    constexpr auto operator<=>(const comparable1 &) const = default;
+  };
+
+  static_assert(std::three_way_comparable<comparable1>);
+  static_assert(has_threeway<comparable1>);
+  static_assert(std::three_way_comparable<inplace_vector<comparable1, 4>>);
+  static_assert(has_threeway<inplace_vector<comparable1, 4>>);
+
+  vec_list<inplace_vector<comparable1, 4>> list{
+      .empty{},
+      .base{{1, 2}, {3, 4}},
+      .copy{{1, 2}, {3, 4}},
+      .greater{{5, 6}, {7, 8}},
+      .lesser{{0, 0}, {0, 0}},
+      .bigger{{1, 2}, {3, 4}, {5, 6}},
+      .smaller{{1, 2}},
+      .greater_smaller{{2, 2}, {3, 3}},
+      .lesser_bigger{{0, 2}, {3, 3}, {4, 4}},
+  };
+
+  runtests(list);
+}
+
+TEST(Compare, threeway_comparable2) {
+
+  struct comparable2 {
+    int a;
+    int b;
+    constexpr bool operator==(const comparable2 &) const = delete;
+    constexpr bool operator<(const comparable2 &other) const {
+      return a < other.a || (a == other.a && b < other.b);
+    };
+  };
+
+  static_assert(!std::three_way_comparable<comparable2>);
+  static_assert(!has_threeway<comparable2>);
+  static_assert(lessthan_comparable<comparable2>);
+  static_assert(std::three_way_comparable<inplace_vector<comparable2, 4>>);
+  static_assert(has_threeway<inplace_vector<comparable2, 4>>);
+
+  vec_list<inplace_vector<comparable2, 4>> list{
+      .empty{},
+      .base{{1, 2}, {3, 4}},
+      .copy{{1, 2}, {3, 4}},
+      .greater{{5, 6}, {7, 8}},
+      .lesser{{0, 0}, {0, 0}},
+      .bigger{{1, 2}, {3, 4}, {5, 6}},
+      .smaller{{1, 2}},
+      .greater_smaller{{2, 2}, {3, 3}},
+      .lesser_bigger{{0, 2}, {3, 3}, {4, 4}},
+  };
+
+  runtests(list);
+}
+
+TEST(Compare, threeway_strong_ordering) {
+
+  struct weaktype {
+    int a;
+    constexpr std::strong_ordering
+    operator<=>(const weaktype &other) const = default;
+  };
+
+  using T = weaktype;
+
+  vec_list<inplace_vector<weaktype, 4>> list{
+      .empty{},
+      .base{T{1}, T{2}, T{3}},
+      .copy{T{1}, T{2}, T{3}},
+      .greater{T{4}, T{5}, T{6}},
+      .lesser{T{0}, T{0}, T{0}},
+      .bigger{T{1}, T{2}, T{3}, T{0}},
+      .smaller{T{1}, T{2}},
+      .greater_smaller{T{2}, T{2}},
+      .lesser_bigger{T{0}, T{2}, T{3}, T{4}},
+  };
+
+  runtests(list);
+}
+
+TEST(Compare, threeway_weak_ordering) {
+
+  struct weaktype {
+    int a;
+    constexpr std::weak_ordering
+    operator<=>(const weaktype &other) const = default;
+  };
+
+  using T = weaktype;
+
+  vec_list<inplace_vector<weaktype, 4>> list{
+      .empty{},
+      .base{T{1}, T{2}, T{3}},
+      .copy{T{1}, T{2}, T{3}},
+      .greater{T{4}, T{5}, T{6}},
+      .lesser{T{0}, T{0}, T{0}},
+      .bigger{T{1}, T{2}, T{3}, T{0}},
+      .smaller{T{1}, T{2}},
+      .greater_smaller{T{2}, T{2}},
+      .lesser_bigger{T{0}, T{2}, T{3}, T{4}},
+  };
+
+  runtests(list);
+}
+
+TEST(Compare, threeway_partial_ordering) {
+
+  struct custom {
+    int a;
+    constexpr auto operator<=>(const custom &other) const {
+      if (a == -1 && other.a == -1)
+        return std::partial_ordering::unordered;
+      return std::partial_ordering(a <=> other.a);
+    }
+
+    constexpr bool operator==(const custom &other) const {
+      if (a == -1 && other.a == -1)
+        return false;
+      return a == other.a;
+    }
+  };
+
+  using T = custom;
+
+  vec_list<inplace_vector<custom, 4>> list{
+      .empty{},
+      .base{T{1}, T{2}, T{3}},
+      .copy{T{1}, T{2}, T{3}},
+      .greater{T{4}, T{5}, T{6}},
+      .lesser{T{0}, T{0}, T{0}},
+      .bigger{T{1}, T{2}, T{3}, T{0}},
+      .smaller{T{1}, T{2}},
+      .greater_smaller{T{2}, T{2}},
+      .lesser_bigger{T{0}, T{2}, T{3}, T{4}},
+  };
+
+  runtests(list);
+
+  T t1{-1};
+  T t2 = t1;
+  EXPECT_EQ(t1 <=> t2, std::partial_ordering::unordered);
+
+  inplace_vector<T, 4> v1{t1};
+  inplace_vector<T, 4> v2{t2};
+
+  EXPECT_EQ(v1 <=> v2, std::partial_ordering::unordered);
+  EXPECT_FALSE(v1 == v2);
+  EXPECT_FALSE(v1 < v2);
+  EXPECT_FALSE(v1 > v2);
+  EXPECT_FALSE(v1 >= v2);
+  EXPECT_FALSE(v1 <= v2);
+}
+
+TEST(Compare, threeway_uncomparable) {
+
+  struct uncomparable1 {
+    int a;
+  };
+
+  static_assert(!std::three_way_comparable<uncomparable1>);
+  static_assert(!has_threeway<uncomparable1>);
+  static_assert(!std::three_way_comparable<inplace_vector<uncomparable1, 4>>);
+  static_assert(!has_threeway<inplace_vector<uncomparable1, 4>>);
+
+  struct uncomparable2 {
+    int a;
+    constexpr bool operator==(const uncomparable2 &) const = default;
+  };
+
+  static_assert(!std::three_way_comparable<uncomparable2>);
+  static_assert(!has_threeway<uncomparable2>);
+  static_assert(!std::three_way_comparable<inplace_vector<uncomparable2, 4>>);
+  static_assert(!has_threeway<inplace_vector<uncomparable2, 4>>);
+
+  struct uncomparable3 {
+    int a;
+    constexpr auto operator<=>(const uncomparable3 &) const = delete;
+  };
+
+  static_assert(!std::three_way_comparable<uncomparable3>);
+  static_assert(!has_threeway<uncomparable3>);
+  static_assert(!std::three_way_comparable<inplace_vector<uncomparable3, 4>>);
+  static_assert(!has_threeway<inplace_vector<uncomparable3, 4>>);
+}


Reviews:
user: login: JeffGarland
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-06T17:42:52Z
user: login: 20162026
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-06T18:10:33Z
user: login: JeffGarland
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-07T11:23:01Z
user: login: JeffGarland
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-07T11:26:58Z
user: login: JeffGarland
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-07T11:28:31Z
user: login: JeffGarland
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-07T11:34:42Z
user: login: 20162026
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-07T11:54:17Z
user: login: 20162026
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-07T11:58:46Z
user: login: 20162026
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-07T12:02:45Z
user: login: 20162026
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-07T12:05:32Z
user: login: JeffGarland
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-07T12:13:49Z
user: login: JeffGarland
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-07T12:36:44Z
user: login: JeffGarland
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-07T14:28:46Z
user: login: JeffGarland
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-07T14:48:11Z
user: login: JeffGarland
body: I'm going to approve this PR -- I'll trust you to make any additional changes as you see fit and merging.  Good work actually fixing this since it was just wrong.  Welcome @20162026 and thanks for your contribution!
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-07T14:55:38Z
user: login: wusatosi
body: Thank you for your contribution. As I understand you still have some progress to make, feel free to @wusatosi to notify me once you're done, I can also approve so you can do that in a separate PR.

Again thank you so much for your contribution!
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T21:02:51Z
user: login: JeffGarland
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-09T01:01:45Z
user: login: wusatosi
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-09T04:02:03Z
user: login: wusatosi
body: Good job!
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-10T15:57:51Z

Review comments:
diff_hunk: @@ -309,6 +310,11 @@ concept container_compatible_range =
 template <typename T, std::size_t N>
 concept satify_constexpr = N == 0 || std::is_trivial_v<T>;
 
+template <typename T>
+concept lessthan_comparable = requires(const T &a, const T &b) {
path: include/beman/inplace_vector/inplace_vector.hpp
user: login: JeffGarland
body: maybe use the standard one? https://en.cppreference.com/w/cpp/named_req/LessThanComparable
created_at: 2025-05-06T16:31:30Z
author_association: MEMBER
diff_hunk: @@ -309,6 +310,11 @@ concept container_compatible_range =
 template <typename T, std::size_t N>
 concept satify_constexpr = N == 0 || std::is_trivial_v<T>;
 
+template <typename T>
+concept lessthan_comparable = requires(const T &a, const T &b) {
path: include/beman/inplace_vector/inplace_vector.hpp
user: login: 20162026
body: do you mean checking for the conditions?
```
For all a, !(a < a) yields true.
If a < b, then !(b < a).
```

becouse there seems to be no stl concept for LessThanComparable (unless I am missing something)
created_at: 2025-05-06T18:10:33Z
author_association: COLLABORATOR
diff_hunk: @@ -309,6 +310,11 @@ concept container_compatible_range =
 template <typename T, std::size_t N>
 concept satify_constexpr = N == 0 || std::is_trivial_v<T>;
 
+template <typename T>
+concept lessthan_comparable = requires(const T &a, const T &b) {
path: include/beman/inplace_vector/inplace_vector.hpp
user: login: JeffGarland
body: Sorry, you're right about that.  However, on further thought, I think we want what was there in the comment before the change: synth-three-way-result constraints.  If you look here: https://eel.is/c++draft/containers#header:%3cinplace_vector%3e (scroll to bottom) you'll see:

```
constexpr friend synth-three-way-result<T>
      operator<=>(const inplace_vector& x, const inplace_vector& y);
```

If you look at what the synth-three-way-result requires here:

https://en.cppreference.com/w/cpp/standard_library/synth-three-way

That would be more like:
```
constexpr auto synth_three_way =
    []<class T, class U>(const T& t, const U& u)
        requires requires
        {
            { t < u } -> std::convertible_to<bool>;
            { u < t } -> std::convertible_to<bool>;
        }
    {
        if constexpr (std::three_way_comparable_with<T, U>)
            return t <=> u;
        else
        {
            if (t < u)
                return std::weak_ordering::less;
            if (u < t)
                return std::weak_ordering::greater;
            return std::weak_ordering::equivalent;
        }
    };
```

created_at: 2025-05-07T11:23:01Z
author_association: MEMBER
diff_hunk: @@ -1001,27 +1007,20 @@ struct inplace_vector
     insert_range(begin(), il);
   }
 
-  constexpr friend int /*synth-three-way-result<T>*/
-  operator<=>(const inplace_vector & x, const inplace_vector & y) {
-    if (x.size() < y.size())
-      return -1;
-    if (x.size() > y.size())
-      return +1;
-
-    bool all_equal = true;
-    bool all_less = true;
-    for (size_type i = 0; i < x.size(); ++i) {
+  constexpr friend auto operator<=>(const inplace_vector &x,
+                                    const inplace_vector &y)
+    requires(std::equality_comparable<T> &&
+             beman::details::inplace_vector::lessthan_comparable<T>)
+  {
+    const auto sz = std::min(x.size(), y.size());
path: include/beman/inplace_vector/inplace_vector.hpp
user: login: JeffGarland
body: So in the prior version the size checking is first -- which short circuits element compares and avoids calling min altogether.  Are we sure we want to change that? 
created_at: 2025-05-07T11:26:58Z
author_association: MEMBER
diff_hunk: @@ -1001,27 +1007,20 @@ struct inplace_vector
     insert_range(begin(), il);
   }
 
-  constexpr friend int /*synth-three-way-result<T>*/
-  operator<=>(const inplace_vector & x, const inplace_vector & y) {
-    if (x.size() < y.size())
-      return -1;
-    if (x.size() > y.size())
-      return +1;
-
-    bool all_equal = true;
-    bool all_less = true;
-    for (size_type i = 0; i < x.size(); ++i) {
+  constexpr friend auto operator<=>(const inplace_vector &x,
+                                    const inplace_vector &y)
+    requires(std::equality_comparable<T> &&
+             beman::details::inplace_vector::lessthan_comparable<T>)
+  {
+    const auto sz = std::min(x.size(), y.size());
+    for (std::size_t i = 0; i < sz; ++i) {
       if (x[i] < y[i])
-        all_equal = false;
-      if (x[i] == y[i])
-        all_less = false;
+        return std::strong_ordering::less;
+      if (y[i] < x[i])
+        return std::strong_ordering::greater;
     }
 
-    if (all_equal)
-      return 0;
-    if (all_less)
-      return -1;
-    return 1;
+    return x.size() <=> y.size();
path: include/beman/inplace_vector/inplace_vector.hpp
user: login: JeffGarland
body: If we refactor to check size upfront then this would be ```return std::strong_ordering::equivalent;```
created_at: 2025-05-07T11:28:31Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,181 @@
+#include <beman/inplace_vector/inplace_vector.hpp>
+#include <gtest/gtest.h>
+
+#include <compare>
+
+using namespace beman;
+
+template <class T>
+concept has_threeway = requires(const T &t) {
+  { t <=> t };
+};
+
+TEST(Compare, threeway_int) {
+  using T = inplace_vector<int, 4>;
+  T empty{};
+  T base{1, 2, 3};
+  T copy = base;
+
+  T greater{4, 5, 6};
+  T lesser{0, 0, 0};
+
+  T bigger{1, 2, 3, 0};
+  T smaller{1, 2};
+
+  T greater_smaller{2, 2};
+  T lesser_bigger{0, 2, 3, 0};
+
+  EXPECT_TRUE((base <=> copy) == 0);
+  EXPECT_TRUE((base <=> greater) < 0);
+  EXPECT_TRUE((base <=> lesser) > 0);
+
+  EXPECT_TRUE((base <=> bigger) < 0);
+  EXPECT_TRUE((base <=> smaller) > 0);
+
+  EXPECT_TRUE((base <=> greater_smaller) < 0);
+  EXPECT_TRUE((base <=> lesser_bigger) > 0);
+}
path: tests/beman/inplace_vector/compare.test.cpp
user: login: JeffGarland
body: maybe also check some synthesized operators as well? 

```
EXPECT_TRUE(smaller < bigger );
EXPECT_TRUE(bigger > smaller );
EXPECT_TRUE(base == copy );
EXPECT_TRUE(base >= copy );
EXPECT_TRUE(copy >= base );
EXPECT_TRUE(copy <= base );
```
created_at: 2025-05-07T11:34:42Z
author_association: MEMBER
diff_hunk: @@ -1001,27 +1007,20 @@ struct inplace_vector
     insert_range(begin(), il);
   }
 
-  constexpr friend int /*synth-three-way-result<T>*/
-  operator<=>(const inplace_vector & x, const inplace_vector & y) {
-    if (x.size() < y.size())
-      return -1;
-    if (x.size() > y.size())
-      return +1;
-
-    bool all_equal = true;
-    bool all_less = true;
-    for (size_type i = 0; i < x.size(); ++i) {
+  constexpr friend auto operator<=>(const inplace_vector &x,
+                                    const inplace_vector &y)
+    requires(std::equality_comparable<T> &&
+             beman::details::inplace_vector::lessthan_comparable<T>)
+  {
+    const auto sz = std::min(x.size(), y.size());
path: include/beman/inplace_vector/inplace_vector.hpp
user: login: 20162026
body: std::vector three way compare checks element value before checking vector size 
https://godbolt.org/z/4ej1cnbf7

also according to https://en.cppreference.com/w/cpp/container/inplace_vector/operator_cmp  size comparesion should be done last (not sure how up to date and correct it is)
created_at: 2025-05-07T11:54:17Z
author_association: COLLABORATOR
diff_hunk: @@ -309,6 +310,11 @@ concept container_compatible_range =
 template <typename T, std::size_t N>
 concept satify_constexpr = N == 0 || std::is_trivial_v<T>;
 
+template <typename T>
+concept lessthan_comparable = requires(const T &a, const T &b) {
path: include/beman/inplace_vector/inplace_vector.hpp
user: login: 20162026
body: std::strong_ordering::XXXX  should fulfill synth-three-way-result<T> condition but I could add a check for it
created_at: 2025-05-07T11:58:46Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,181 @@
+#include <beman/inplace_vector/inplace_vector.hpp>
+#include <gtest/gtest.h>
+
+#include <compare>
+
+using namespace beman;
+
+template <class T>
+concept has_threeway = requires(const T &t) {
+  { t <=> t };
+};
+
+TEST(Compare, threeway_int) {
+  using T = inplace_vector<int, 4>;
+  T empty{};
+  T base{1, 2, 3};
+  T copy = base;
+
+  T greater{4, 5, 6};
+  T lesser{0, 0, 0};
+
+  T bigger{1, 2, 3, 0};
+  T smaller{1, 2};
+
+  T greater_smaller{2, 2};
+  T lesser_bigger{0, 2, 3, 0};
+
+  EXPECT_TRUE((base <=> copy) == 0);
+  EXPECT_TRUE((base <=> greater) < 0);
+  EXPECT_TRUE((base <=> lesser) > 0);
+
+  EXPECT_TRUE((base <=> bigger) < 0);
+  EXPECT_TRUE((base <=> smaller) > 0);
+
+  EXPECT_TRUE((base <=> greater_smaller) < 0);
+  EXPECT_TRUE((base <=> lesser_bigger) > 0);
+}
path: tests/beman/inplace_vector/compare.test.cpp
user: login: 20162026
body: I can add more test, but imho they are excessive as at that point we are testing if compiler properly converts <=> into derived operators
created_at: 2025-05-07T12:02:45Z
author_association: COLLABORATOR
diff_hunk: @@ -1001,27 +1007,20 @@ struct inplace_vector
     insert_range(begin(), il);
   }
 
-  constexpr friend int /*synth-three-way-result<T>*/
-  operator<=>(const inplace_vector & x, const inplace_vector & y) {
-    if (x.size() < y.size())
-      return -1;
-    if (x.size() > y.size())
-      return +1;
-
-    bool all_equal = true;
-    bool all_less = true;
-    for (size_type i = 0; i < x.size(); ++i) {
+  constexpr friend auto operator<=>(const inplace_vector &x,
+                                    const inplace_vector &y)
+    requires(std::equality_comparable<T> &&
+             beman::details::inplace_vector::lessthan_comparable<T>)
+  {
+    const auto sz = std::min(x.size(), y.size());
+    for (std::size_t i = 0; i < sz; ++i) {
       if (x[i] < y[i])
-        all_equal = false;
-      if (x[i] == y[i])
-        all_less = false;
+        return std::strong_ordering::less;
+      if (y[i] < x[i])
+        return std::strong_ordering::greater;
     }
 
-    if (all_equal)
-      return 0;
-    if (all_less)
-      return -1;
-    return 1;
+    return x.size() <=> y.size();
path: include/beman/inplace_vector/inplace_vector.hpp
user: login: 20162026
body: as previously mentioned element comparison should be done before size comparesion
created_at: 2025-05-07T12:05:32Z
author_association: COLLABORATOR
diff_hunk: @@ -1001,27 +1007,20 @@ struct inplace_vector
     insert_range(begin(), il);
   }
 
-  constexpr friend int /*synth-three-way-result<T>*/
-  operator<=>(const inplace_vector & x, const inplace_vector & y) {
-    if (x.size() < y.size())
-      return -1;
-    if (x.size() > y.size())
-      return +1;
-
-    bool all_equal = true;
-    bool all_less = true;
-    for (size_type i = 0; i < x.size(); ++i) {
+  constexpr friend auto operator<=>(const inplace_vector &x,
+                                    const inplace_vector &y)
+    requires(std::equality_comparable<T> &&
+             beman::details::inplace_vector::lessthan_comparable<T>)
+  {
+    const auto sz = std::min(x.size(), y.size());
path: include/beman/inplace_vector/inplace_vector.hpp
user: login: JeffGarland
body: Well I'm not sure where they got that, but it's for sure not in the standard anywhere that I can find.  

https://eel.is/c++draft/inplace.vector
https://eel.is/c++draft/sequence.reqmts

Keep in mind that the paper author wrote that code -- which of course also doesn't ensure it's correct. 

Let's think this out.  If we have a = {1} and b={1,2} then a < b and b > a. if c = {3} then c > a, but is c > b?  If c < b then we can do the size check and ignore the contents.
created_at: 2025-05-07T12:13:49Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,181 @@
+#include <beman/inplace_vector/inplace_vector.hpp>
+#include <gtest/gtest.h>
+
+#include <compare>
+
+using namespace beman;
+
+template <class T>
+concept has_threeway = requires(const T &t) {
+  { t <=> t };
+};
+
+TEST(Compare, threeway_int) {
+  using T = inplace_vector<int, 4>;
+  T empty{};
+  T base{1, 2, 3};
+  T copy = base;
+
+  T greater{4, 5, 6};
+  T lesser{0, 0, 0};
+
+  T bigger{1, 2, 3, 0};
+  T smaller{1, 2};
+
+  T greater_smaller{2, 2};
+  T lesser_bigger{0, 2, 3, 0};
+
+  EXPECT_TRUE((base <=> copy) == 0);
+  EXPECT_TRUE((base <=> greater) < 0);
+  EXPECT_TRUE((base <=> lesser) > 0);
+
+  EXPECT_TRUE((base <=> bigger) < 0);
+  EXPECT_TRUE((base <=> smaller) > 0);
+
+  EXPECT_TRUE((base <=> greater_smaller) < 0);
+  EXPECT_TRUE((base <=> lesser_bigger) > 0);
+}
path: tests/beman/inplace_vector/compare.test.cpp
user: login: JeffGarland
body: I'd like to see some tests because the synthesized comparisons early on did some odd things and created a bunch of issues -- the committee had to fix that post-hoc. So it's possible there's still some older compilers that haven't implemented -- although it's quite possible these aren't impacted.  

If you want to keep the tests smaller I'd suggest removing the operator<=> tests and only testing the actual comparisons users would call.
created_at: 2025-05-07T12:36:44Z
author_association: MEMBER
diff_hunk: @@ -1001,27 +1007,20 @@ struct inplace_vector
     insert_range(begin(), il);
   }
 
-  constexpr friend int /*synth-three-way-result<T>*/
-  operator<=>(const inplace_vector & x, const inplace_vector & y) {
-    if (x.size() < y.size())
-      return -1;
-    if (x.size() > y.size())
-      return +1;
-
-    bool all_equal = true;
-    bool all_less = true;
-    for (size_type i = 0; i < x.size(); ++i) {
+  constexpr friend auto operator<=>(const inplace_vector &x,
+                                    const inplace_vector &y)
+    requires(std::equality_comparable<T> &&
+             beman::details::inplace_vector::lessthan_comparable<T>)
+  {
+    const auto sz = std::min(x.size(), y.size());
path: include/beman/inplace_vector/inplace_vector.hpp
user: login: JeffGarland
body: The answer if a < b < c -- after an assist here's a godbolt showing what vector does https://godbolt.org/z/b5hfr56EE

The implementation before was incorrect -- the binding requirement is here: https://timsong-cpp.github.io/cppwp/container.opt.reqmts
created_at: 2025-05-07T14:28:46Z
author_association: MEMBER
diff_hunk: @@ -1001,27 +1007,20 @@ struct inplace_vector
     insert_range(begin(), il);
   }
 
-  constexpr friend int /*synth-three-way-result<T>*/
-  operator<=>(const inplace_vector & x, const inplace_vector & y) {
-    if (x.size() < y.size())
-      return -1;
-    if (x.size() > y.size())
-      return +1;
-
-    bool all_equal = true;
-    bool all_less = true;
-    for (size_type i = 0; i < x.size(); ++i) {
+  constexpr friend auto operator<=>(const inplace_vector &x,
+                                    const inplace_vector &y)
+    requires(std::equality_comparable<T> &&
+             beman::details::inplace_vector::lessthan_comparable<T>)
+  {
+    const auto sz = std::min(x.size(), y.size());
+    for (std::size_t i = 0; i < sz; ++i) {
       if (x[i] < y[i])
-        all_equal = false;
-      if (x[i] == y[i])
-        all_less = false;
+        return std::strong_ordering::less;
+      if (y[i] < x[i])
+        return std::strong_ordering::greater;
     }
 
-    if (all_equal)
-      return 0;
-    if (all_less)
-      return -1;
-    return 1;
+    return x.size() <=> y.size();
path: include/beman/inplace_vector/inplace_vector.hpp
user: login: JeffGarland
body: yep, I agree with that now after working thru it.
created_at: 2025-05-07T14:48:11Z
author_association: MEMBER
diff_hunk: @@ -11,6 +11,10 @@ concept has_threeway = requires(const T &t) {
   { t <=> t };
 };
 
+template <class T>
+concept lessthan_comparable =
+    beman::details::inplace_vector::lessthan_comparable<T>;
path: tests/beman/inplace_vector/compare.test.cpp
user: login: JeffGarland
body: It strikes me that the namespace is backwards here -- should be beman::inplace_vector::detail -- but that's an existing issue of course. And maybe something we don't address clearly in the standard.
created_at: 2025-05-09T01:01:45Z
author_association: MEMBER
diff_hunk: @@ -11,6 +11,10 @@ concept has_threeway = requires(const T &t) {
   { t <=> t };
 };
 
+template <class T>
+concept lessthan_comparable =
+    beman::details::inplace_vector::lessthan_comparable<T>;
path: tests/beman/inplace_vector/compare.test.cpp
user: login: wusatosi
body: I might be wrong here, but beman::inplace_vector clashes with the inplace_vector struct under the beman namespace.
created_at: 2025-05-09T04:02:03Z
author_association: MEMBER

PR comments:
user: login: wusatosi
created_at: 2025-05-06T16:48:05Z
body: Thank you for your contribution! I will review this later!
author_association: MEMBER
user: login: 20162026
created_at: 2025-05-07T20:57:01Z
body: Just noticed that my suggestion will not properly handle unorderable values eg.  nan("") == nan(""). 
I will try to come up with a better solution tomorrow
author_association: COLLABORATOR
user: login: JeffGarland
created_at: 2025-05-08T01:22:46Z
body: I think the pre-conditions here mean that if you have nans you're in violation and it's UB
author_association: MEMBER
user: login: 20162026
created_at: 2025-05-08T07:46:21Z
body: I understand precondition as: _T models three_way_comparable **OR** (< is defined for values of type T and < is a total ordering relationship)_

nan (float/double) is three_way_comparable with std::partial_ordering, furthermore std::vector nan compare results in _std::partial_ordering::unordered_
author_association: COLLABORATOR
user: login: wusatosi
created_at: 2025-05-08T16:26:01Z
body: > failed in some cases.

Can you list out cases that fails with the original implementation?
Those could be great unit tests!
author_association: MEMBER
user: login: 20162026
created_at: 2025-05-08T17:09:34Z
body: > > failed in some cases.
> 
> Can you list out cases that fails with the original implementation? Those could be great unit tests!

almost all of the issues I could think of are already covered by tests. 
But to name a few, original:
- did size compare before value compare
- had incorrect return type
- would not work with unorderable values
- did not check T preconditions
- did not break the loop after finding the first nonequal element
author_association: COLLABORATOR
user: login: wusatosi
created_at: 2025-05-08T17:16:37Z
body: > > > failed in some cases.
> > 
> > Can you list out cases that fails with the original implementation? Those could be great unit tests!
> 
> almost all of the issues I could think of are already covered by tests. 
> But to name a few, original:
> - did size compare before value compare
> - had incorrect return type
> - would not work with unorderable values
> - did not check T preconditions
> - did not break the loop after finding the first nonequal element

Oh I didn't realize you added test as well!! This is fantastic! 
author_association: MEMBER
user: login: 20162026
created_at: 2025-05-08T22:35:53Z
body: @wusatosi Im done with the changes
author_association: COLLABORATOR