[libcxx-commits] [libcxx] [libc++][test] Fix construction and comparison for testing allocators (PR #212702)
A. Jiang via libcxx-commits
libcxx-commits at lists.llvm.org
Wed Jul 29 01:05:10 PDT 2026
https://github.com/frederick-vs-ja updated https://github.com/llvm/llvm-project/pull/212702
>From 334b2c587d6d4ecc4da8125fad80b897c77dfadb Mon Sep 17 00:00:00 2001
From: "A. Jiang" <de34 at live.cn>
Date: Wed, 29 Jul 2026 14:36:34 +0800
Subject: [PATCH] [libc++][test] Fix construction and comparison for testing
allocators
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.
---
libcxx/test/support/min_allocator.h | 98 ++++++--
.../test.support/test_allocators.pass.cpp | 224 ++++++++++++++++++
libcxx/test/support/test_allocator.h | 56 ++++-
3 files changed, 352 insertions(+), 26 deletions(-)
create mode 100644 libcxx/test/support/test.support/test_allocators.pass.cpp
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..eb16fb9944831 100644
--- a/libcxx/test/support/test_allocator.h
+++ b/libcxx/test/support/test_allocator.h
@@ -195,8 +195,15 @@ 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) {
+ const test_allocator& y2 = y;
+ return x.data_ == y2.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 +266,15 @@ 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) {
+ const test_allocator& y2 = y;
+ return x.data_ == y2.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 +298,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 +379,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 +538,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;
More information about the libcxx-commits
mailing list