[libcxx-commits] [libcxx] [libc++][test] Fix construction and comparison for testing allocators (PR #212702)

via libcxx-commits libcxx-commits at lists.llvm.org
Wed Jul 29 00:43:20 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-libcxx

Author: A. Jiang (frederick-vs-ja)

<details>
<summary>Changes</summary>

Previously, there were several issues in the allocators provided by `min_allocator.h` and `test_allocator.h`.

1. Some allocators did not support heterogenous rebinding construction, and thus failed to meet the Cpp17Allocator named requirements.
2. Some allocators only had `operator==`. This was fine since C++20 but not in C++17 where there were no rewritten candidates of `!=`.
3. Many equality operators were non-template and homogeneous. This caused ambiguity since C++20 due to rewritten candidates.

This patch fixes these issues by
- adding missing constructors,
- adding missing `operator!=` (in pre-C++20 modes), and
- making `operator==` and some `operator!=` templates.

Note that it is intended that `operator==`'s for `test_allocator` perform seemingly redundant constructions to avoid behavioral change as possible because implicit conversion was performed before this patch.

A regression test is added.

---
Full diff: https://github.com/llvm/llvm-project/pull/212702.diff


3 Files Affected:

- (modified) libcxx/test/support/min_allocator.h (+80-18) 
- (added) libcxx/test/support/test.support/test_allocators.pass.cpp (+224) 
- (modified) libcxx/test/support/test_allocator.h (+46-8) 


``````````diff
diff --git a/libcxx/test/support/min_allocator.h b/libcxx/test/support/min_allocator.h
index 07603425f0668..193fc5b4d81c4 100644
--- a/libcxx/test/support/min_allocator.h
+++ b/libcxx/test/support/min_allocator.h
@@ -35,8 +35,14 @@ class bare_allocator {
 
   void deallocate(T* p, std::size_t) { return ::operator delete(static_cast<void*>(p)); }
 
-  friend bool operator==(bare_allocator, bare_allocator) { return true; }
-  friend bool operator!=(bare_allocator x, bare_allocator y) { return !(x == y); }
+  template <class U>
+  friend bool operator==(bare_allocator, bare_allocator<U>) {
+    return true;
+  }
+  template <class U>
+  friend bool operator!=(bare_allocator x, bare_allocator<U> y) {
+    return !(x == y);
+  }
 };
 
 template <class T>
@@ -65,8 +71,14 @@ class no_default_allocator {
 
   TEST_CONSTEXPR_CXX20 void deallocate(T* p, std::size_t n) { std::allocator<T>().deallocate(p, n); }
 
-  friend TEST_CONSTEXPR bool operator==(no_default_allocator, no_default_allocator) { return true; }
-  friend TEST_CONSTEXPR bool operator!=(no_default_allocator x, no_default_allocator y) { return !(x == y); }
+  template <class U>
+  friend TEST_CONSTEXPR bool operator==(no_default_allocator, no_default_allocator<U>) {
+    return true;
+  }
+  template <class U>
+  friend TEST_CONSTEXPR bool operator!=(no_default_allocator x, no_default_allocator<U> y) {
+    return !(x == y);
+  }
 };
 
 struct malloc_allocator_base {
@@ -118,8 +130,14 @@ class malloc_allocator : public malloc_allocator_base {
     std::free(static_cast<void*>(p));
   }
 
-  friend bool operator==(malloc_allocator, malloc_allocator) { return true; }
-  friend bool operator!=(malloc_allocator x, malloc_allocator y) { return !(x == y); }
+  template <class U>
+  friend bool operator==(malloc_allocator, malloc_allocator<U>) {
+    return true;
+  }
+  template <class U>
+  friend bool operator!=(malloc_allocator x, malloc_allocator<U> y) {
+    return !(x == y);
+  }
 };
 
 template <class T>
@@ -129,6 +147,10 @@ struct cpp03_allocator : bare_allocator<T> {
 
   static bool construct_called;
 
+  cpp03_allocator() TEST_NOEXCEPT {}
+  template <class U>
+  explicit cpp03_allocator(const cpp03_allocator<U>&) TEST_NOEXCEPT {}
+
   // Returned value is not used but it's not prohibited.
   pointer construct(pointer p, const value_type& val) {
     ::new (p) value_type(val);
@@ -148,6 +170,10 @@ struct cpp03_overload_allocator : bare_allocator<T> {
 
   static bool construct_called;
 
+  cpp03_overload_allocator() TEST_NOEXCEPT {}
+  template <class U>
+  explicit cpp03_overload_allocator(const cpp03_overload_allocator<U>&) TEST_NOEXCEPT {}
+
   void construct(pointer p, const value_type& val) { construct(p, val, std::is_class<T>()); }
   void construct(pointer p, const value_type& val, std::true_type) {
     ::new (p) value_type(val);
@@ -395,8 +421,14 @@ class min_allocator {
 
   TEST_CONSTEXPR_CXX20 void deallocate(pointer p, std::size_t n) { std::allocator<T>().deallocate(p.ptr_, n); }
 
-  TEST_CONSTEXPR_CXX20 friend bool operator==(min_allocator, min_allocator) { return true; }
-  TEST_CONSTEXPR_CXX20 friend bool operator!=(min_allocator x, min_allocator y) { return !(x == y); }
+  template <class U>
+  TEST_CONSTEXPR_CXX20 friend bool operator==(min_allocator, min_allocator<U>) {
+    return true;
+  }
+  template <class U>
+  TEST_CONSTEXPR_CXX20 friend bool operator!=(min_allocator x, min_allocator<U> y) {
+    return !(x == y);
+  }
 };
 
 template <class T>
@@ -416,8 +448,14 @@ class complete_type_allocator {
 
   TEST_CONSTEXPR_CXX20 void deallocate(T* p, std::size_t n) { std::allocator<T>().deallocate(p, n); }
 
-  TEST_CONSTEXPR_CXX20 friend bool operator==(complete_type_allocator, complete_type_allocator) { return true; }
-  TEST_CONSTEXPR_CXX20 friend bool operator!=(complete_type_allocator, complete_type_allocator) { return false; }
+  template <class U>
+  TEST_CONSTEXPR_CXX20 friend bool operator==(complete_type_allocator, complete_type_allocator<U>) {
+    return true;
+  }
+  template <class U>
+  TEST_CONSTEXPR_CXX20 friend bool operator!=(complete_type_allocator, complete_type_allocator<U>) {
+    return false;
+  }
 };
 
 template <class T>
@@ -435,8 +473,14 @@ class explicit_allocator
 
   TEST_CONSTEXPR_CXX20 void deallocate(T* p, std::size_t n) { std::allocator<T>().deallocate(p, n); }
 
-  TEST_CONSTEXPR_CXX20 friend bool operator==(explicit_allocator, explicit_allocator) { return true; }
-  TEST_CONSTEXPR_CXX20 friend bool operator!=(explicit_allocator x, explicit_allocator y) { return !(x == y); }
+  template <class U>
+  TEST_CONSTEXPR_CXX20 friend bool operator==(explicit_allocator, explicit_allocator<U>) {
+    return true;
+  }
+  template <class U>
+  TEST_CONSTEXPR_CXX20 friend bool operator!=(explicit_allocator x, explicit_allocator<U> y) {
+    return !(x == y);
+  }
 };
 
 template <class T>
@@ -454,8 +498,14 @@ class unaligned_allocator {
 
   TEST_CONSTEXPR_CXX20 void deallocate(T* p, std::size_t n) { std::allocator<T>().deallocate(p - 1, n + 1); }
 
-  TEST_CONSTEXPR_CXX20 friend bool operator==(unaligned_allocator, unaligned_allocator) { return true; }
-  TEST_CONSTEXPR_CXX20 friend bool operator!=(unaligned_allocator x, unaligned_allocator y) { return !(x == y); }
+  template <class U>
+  TEST_CONSTEXPR_CXX20 friend bool operator==(unaligned_allocator, unaligned_allocator<U>) {
+    return true;
+  }
+  template <class U>
+  TEST_CONSTEXPR_CXX20 friend bool operator!=(unaligned_allocator x, unaligned_allocator<U> y) {
+    return !(x == y);
+  }
 };
 
 template <class T>
@@ -482,8 +532,14 @@ class safe_allocator {
     std::allocator<T>().deallocate(p, n);
   }
 
-  TEST_CONSTEXPR_CXX20 friend bool operator==(safe_allocator, safe_allocator) { return true; }
-  TEST_CONSTEXPR_CXX20 friend bool operator!=(safe_allocator x, safe_allocator y) { return !(x == y); }
+  template <class U>
+  TEST_CONSTEXPR_CXX20 friend bool operator==(safe_allocator, safe_allocator<U>) {
+    return true;
+  }
+  template <class U>
+  TEST_CONSTEXPR_CXX20 friend bool operator!=(safe_allocator x, safe_allocator<U> y) {
+    return !(x == y);
+  }
 };
 
 template <std::size_t MaxSize, class T>
@@ -510,8 +566,14 @@ struct tiny_size_allocator {
 
   TEST_CONSTEXPR_CXX20 size_type max_size() const { return MaxSize; }
 
-  friend bool operator==(tiny_size_allocator, tiny_size_allocator) { return true; }
-  friend bool operator!=(tiny_size_allocator, tiny_size_allocator) { return false; }
+  template <class U>
+  friend TEST_CONSTEXPR_CXX20 bool operator==(tiny_size_allocator, tiny_size_allocator<MaxSize, U>) {
+    return true;
+  }
+  template <class U>
+  friend TEST_CONSTEXPR_CXX20 bool operator!=(tiny_size_allocator, tiny_size_allocator<MaxSize, U>) {
+    return false;
+  }
 };
 
 #endif // MIN_ALLOCATOR_H
diff --git a/libcxx/test/support/test.support/test_allocators.pass.cpp b/libcxx/test/support/test.support/test_allocators.pass.cpp
new file mode 100644
index 0000000000000..574bfc0be1e92
--- /dev/null
+++ b/libcxx/test/support/test.support/test_allocators.pass.cpp
@@ -0,0 +1,224 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// Makes sure that test allocators in "min_allocator.h" and "test_allocator.h" properly support
+// heterogeneous construction and comparison.
+
+#include <cassert>
+#include <memory>
+
+#include "min_allocator.h"
+#include "test_allocator.h"
+#include "test_macros.h"
+
+#if TEST_STD_VER >= 11
+template <class A, class U>
+struct rebind_alloc {
+  using type = typename std::allocator_traits<A>::template rebind_alloc<U>;
+};
+#else
+template <class A, class U>
+struct rebind_alloc {
+  typedef typename std::allocator_traits<A>::template rebind_alloc<U>::other type;
+};
+#endif
+
+TEST_CONSTEXPR_CXX20 bool test() {
+  {
+    ASSERT_SAME_TYPE(rebind_alloc<min_allocator<int>, char>::type, min_allocator<char>);
+    min_allocator<int> a1;
+    min_allocator<char> a2(a1);
+
+    assert(a1 == a1);
+    assert(a1 == a2);
+    assert(!(a1 != a1));
+    assert(!(a1 != a2));
+  }
+  {
+    ASSERT_SAME_TYPE(rebind_alloc<complete_type_allocator<int>, char>::type, complete_type_allocator<char>);
+    complete_type_allocator<int> a1;
+    complete_type_allocator<char> a2(a1);
+
+    assert(a1 == a1);
+    assert(a1 == a2);
+    assert(!(a1 != a1));
+    assert(!(a1 != a2));
+  }
+  {
+    ASSERT_SAME_TYPE(rebind_alloc<explicit_allocator<int>, char>::type, explicit_allocator<char>);
+    explicit_allocator<int> a1;
+    explicit_allocator<char> a2(a1);
+
+    assert(a1 == a1);
+    assert(a1 == a2);
+    assert(!(a1 != a1));
+    assert(!(a1 != a2));
+  }
+  {
+    ASSERT_SAME_TYPE(rebind_alloc<unaligned_allocator<unsigned char>, char>::type, unaligned_allocator<char>);
+    unaligned_allocator<unsigned char> a1;
+    unaligned_allocator<char> a2(a1);
+
+    assert(a1 == a1);
+    assert(a1 == a2);
+    assert(!(a1 != a1));
+    assert(!(a1 != a2));
+  }
+  {
+    ASSERT_SAME_TYPE(rebind_alloc<safe_allocator<int>, char>::type, safe_allocator<char>);
+    safe_allocator<int> a1;
+    safe_allocator<char> a2(a1);
+
+    assert(a1 == a1);
+    assert(a1 == a2);
+    assert(!(a1 != a1));
+    assert(!(a1 != a2));
+  }
+  {
+    ASSERT_SAME_TYPE(rebind_alloc<tiny_size_allocator<128, int>, char>::type, tiny_size_allocator<128, char>);
+    tiny_size_allocator<128, int> a1;
+    tiny_size_allocator<128, char> a2(a1);
+
+    assert(a1 == a1);
+    assert(a1 == a2);
+    assert(!(a1 != a1));
+    assert(!(a1 != a2));
+  }
+  {
+    ASSERT_SAME_TYPE(rebind_alloc<test_allocator<int>, char>::type, test_allocator<char>);
+    test_allocator<int> a1(17);
+    test_allocator<char> a2(a1);
+    test_allocator<int> a3(29);
+    test_allocator<char> a4(a3);
+
+    assert(a1 == a1);
+    assert(a1 == a2);
+    assert(!(a1 != a1));
+    assert(!(a1 != a2));
+    assert(!(a1 == a3));
+    assert(!(a1 == a4));
+    assert(a1 != a3);
+    assert(a1 != a4);
+  }
+  {
+    ASSERT_SAME_TYPE(rebind_alloc<other_allocator<int>, char>::type, other_allocator<char>);
+    other_allocator<int> a1(17);
+    other_allocator<char> a2(a1);
+    other_allocator<int> a3(29);
+    other_allocator<char> a4(a3);
+
+    assert(a1 == a1);
+    assert(a1 == a2);
+    assert(!(a1 != a1));
+    assert(!(a1 != a2));
+    assert(!(a1 == a3));
+    assert(!(a1 == a4));
+    assert(a1 != a3);
+    assert(a1 != a4);
+  }
+  {
+    ASSERT_SAME_TYPE(rebind_alloc<TaggingAllocator<int>, Tag_X>::type, TaggingAllocator<Tag_X>);
+    TaggingAllocator<int> a1;
+    TaggingAllocator<Tag_X> a2(a1);
+
+    assert(a1 == a1);
+    assert(a1 == a2);
+    assert(!(a1 != a1));
+    assert(!(a1 != a2));
+  }
+  {
+    ASSERT_SAME_TYPE(rebind_alloc<limited_allocator<int, 42>, char>::type, limited_allocator<char, 42>);
+    limited_allocator<int, 42> a1;
+    limited_allocator<char, 42> a2(a1);
+
+    assert(a1 == a1);
+    assert(a1 == a2);
+    assert(!(a1 != a1));
+    assert(!(a1 != a2));
+  }
+  return true;
+}
+
+int main(int, char**) {
+  test();
+#if TEST_STD_VER >= 20
+  static_assert(test());
+#endif
+
+  // constexpr-unfriendly allocators
+
+  {
+    ASSERT_SAME_TYPE(rebind_alloc<bare_allocator<int>, char>::type, bare_allocator<char>);
+    bare_allocator<int> a1;
+    bare_allocator<char> a2(a1);
+
+    assert(a1 == a1);
+    assert(a1 == a2);
+    assert(!(a1 != a1));
+    assert(!(a1 != a2));
+  }
+  {
+    ASSERT_SAME_TYPE(rebind_alloc<no_default_allocator<int>, char>::type, no_default_allocator<char>);
+    no_default_allocator<int> a1 = no_default_allocator<int>::create();
+    no_default_allocator<char> a2(a1);
+
+    assert(a1 == a1);
+    assert(a1 == a2);
+    assert(!(a1 != a1));
+    assert(!(a1 != a2));
+  }
+  {
+    ASSERT_SAME_TYPE(rebind_alloc<malloc_allocator<int>, char>::type, malloc_allocator<char>);
+    malloc_allocator_base::disable_default_constructor = false;
+    malloc_allocator<int> a1;
+    malloc_allocator_base::disable_default_constructor = true;
+    malloc_allocator<char> a2(a1);
+
+    assert(a1 == a1);
+    assert(a1 == a2);
+    assert(!(a1 != a1));
+    assert(!(a1 != a2));
+  }
+  {
+    ASSERT_SAME_TYPE(rebind_alloc<cpp03_allocator<int>, char>::type, cpp03_allocator<char>);
+    cpp03_allocator<int> a1;
+    cpp03_allocator<char> a2(a1);
+
+    assert(a1 == a1);
+    assert(a1 == a2);
+    assert(!(a1 != a1));
+    assert(!(a1 != a2));
+  }
+  {
+    ASSERT_SAME_TYPE(rebind_alloc<cpp03_overload_allocator<int>, char>::type, cpp03_overload_allocator<char>);
+    cpp03_overload_allocator<int> a1;
+    cpp03_overload_allocator<char> a2(a1);
+
+    assert(a1 == a1);
+    assert(a1 == a2);
+    assert(!(a1 != a1));
+    assert(!(a1 != a2));
+  }
+  {
+    ASSERT_SAME_TYPE(rebind_alloc<SocccAllocator<int>, char>::type, SocccAllocator<char>);
+    SocccAllocator<int> a1(17);
+    SocccAllocator<char> a2(a1);
+    SocccAllocator<int> a3(29);
+    SocccAllocator<char> a4(a3);
+
+    assert(a1 == a1);
+    assert(a1 == a2);
+    assert(!(a1 != a1));
+    assert(!(a1 != a2));
+    assert(a1 == a3);
+    assert(a1 == a4);
+    assert(!(a1 != a3));
+    assert(!(a1 != a4));
+  }
+  return 0;
+}
diff --git a/libcxx/test/support/test_allocator.h b/libcxx/test/support/test_allocator.h
index f8b622d7f9520..4a2da8b1b1390 100644
--- a/libcxx/test/support/test_allocator.h
+++ b/libcxx/test/support/test_allocator.h
@@ -195,8 +195,14 @@ class test_allocator {
       ++stats_->destroy_count;
     p->~T();
   }
-  TEST_CONSTEXPR friend bool operator==(const test_allocator& x, const test_allocator& y) { return x.data_ == y.data_; }
-  TEST_CONSTEXPR friend bool operator!=(const test_allocator& x, const test_allocator& y) { return !(x == y); }
+  template <class U>
+  TEST_CONSTEXPR friend bool operator==(const test_allocator& x, const test_allocator<U>& y) {
+    return x.data_ == test_allocator(y).data_;
+  }
+  template <class U>
+  TEST_CONSTEXPR friend bool operator!=(const test_allocator& x, const test_allocator<U>& y) {
+    return !(x == y);
+  }
 
   TEST_CONSTEXPR int get_data() const { return data_; }
   TEST_CONSTEXPR int get_id() const { return id_; }
@@ -259,8 +265,14 @@ class test_allocator<void> {
   TEST_CONSTEXPR int get_id() const { return id_; }
   TEST_CONSTEXPR int get_data() const { return data_; }
 
-  TEST_CONSTEXPR friend bool operator==(const test_allocator& x, const test_allocator& y) { return x.data_ == y.data_; }
-  TEST_CONSTEXPR friend bool operator!=(const test_allocator& x, const test_allocator& y) { return !(x == y); }
+  template <class U>
+  TEST_CONSTEXPR friend bool operator==(const test_allocator& x, const test_allocator<U>& y) {
+    return x.data_ == test_allocator(y).data_;
+  }
+  template <class U>
+  TEST_CONSTEXPR friend bool operator!=(const test_allocator& x, const test_allocator<U>& y) {
+    return !(x == y);
+  }
 };
 
 template <class T>
@@ -284,11 +296,15 @@ class other_allocator {
 
   TEST_CONSTEXPR_CXX14 other_allocator select_on_container_copy_construction() const { return other_allocator(-2); }
 
-  TEST_CONSTEXPR_CXX14 friend bool operator==(const other_allocator& x, const other_allocator& y) {
-    return x.data_ == y.data_;
+  template <class U>
+  TEST_CONSTEXPR_CXX14 friend bool operator==(const other_allocator& x, const other_allocator<U>& y) {
+    return x.data_ == y.get_data();
   }
 
-  TEST_CONSTEXPR_CXX14 friend bool operator!=(const other_allocator& x, const other_allocator& y) { return !(x == y); }
+  template <class U>
+  TEST_CONSTEXPR_CXX14 friend bool operator!=(const other_allocator& x, const other_allocator<U>& y) {
+    return !(x == y);
+  }
   TEST_CONSTEXPR int get_data() const { return data_; }
 
   typedef std::true_type propagate_on_container_copy_assignment;
@@ -361,6 +377,18 @@ class TaggingAllocator {
 
   TEST_CONSTEXPR_CXX20 T* allocate(std::size_t n) { return std::allocator<T>().allocate(n); }
   TEST_CONSTEXPR_CXX20 void deallocate(T* p, std::size_t n) { std::allocator<T>().deallocate(p, n); }
+
+  template <class U>
+  TEST_CONSTEXPR friend bool operator==(const TaggingAllocator&, const TaggingAllocator<U>&) {
+    return true;
+  }
+
+#if TEST_STD_VER < 20
+  template <class U>
+  TEST_CONSTEXPR friend bool operator!=(const TaggingAllocator&, const TaggingAllocator<U>&) {
+    return false;
+  }
+#endif
 };
 
 template <std::size_t MaxAllocs>
@@ -508,7 +536,17 @@ struct SocccAllocator {
 
   SocccAllocator select_on_container_copy_construction() const { return SocccAllocator(count_ + 1); }
 
-  bool operator==(const SocccAllocator&) const { return true; }
+  template <class U>
+  bool operator==(const SocccAllocator<U>&) const {
+    return true;
+  }
+
+#if TEST_STD_VER < 20
+  template <class U>
+  bool operator!=(const SocccAllocator<U>&) const {
+    return false;
+  }
+#endif
 
   using propagate_on_container_copy_assignment = std::false_type;
   using propagate_on_container_move_assignment = std::false_type;

``````````

</details>


https://github.com/llvm/llvm-project/pull/212702


More information about the libcxx-commits mailing list