Cover the full C++26 std::hazard_pointer API surface with DejaGnu
tests under 30_threads/hazard_pointer/.
Functional tests (one file per API / concern):
* make_hazard_pointer.cc -- factory, slot accounting, pool growth.
* ctor.cc -- default ctor, move ctor, destructor, slot accounting.
* empty.cc -- empty() across every state transition.
* move_assign.cc -- transfers ownership, releases prior slot, self-
assignment is a no-op.
* swap.cc -- member swap, free swap, protection follows the slot.
* protect.cc -- returns current value, null source, slot ownership,
re-protect updates the slot.
* try_protect.cc -- success / failure return, ptr output, agreement
with protect() on a stable pointer.
* reset_protection.cc -- both overloads, idempotence, typed-pointer
overload publishes a hazard directly.
* retire.cc -- single retire, multiple retires, threshold auto-
synchronize fires when retired count exceeds 2 * active slots.
* custom_deleter.cc -- functor deleter invoked, default-constructed
deleter, deleter suppressed while protected.
Compile-time property tests:
* noexcept.cc -- static_asserts for every noexcept-annotated API.
* type_constraints.cc -- _Protectable concept for derived / non-
derived types, retire() deleter constraints.
Multi-threaded tests:
* concurrent.cc -- readers + writer, multi-writer multi-reader,
high-contention; run under TSan to verify no data races and no
use-after-free.
* thread_exit.cc -- ~_RetireListNode drains the retire list on
thread exit; protected survivors offload to the orphan list.
Negative compile tests (dg-error + dg-prune-output for cascade
diagnostics past the static_assert / -Werror=unused-result):
* protect_neg.cc -- protect<int>() and protect<NonHazard>()
rejected by the _Protectable concept.
* retire_neg.cc -- retire() rejected when hazard_pointer_obj_base
is a private base.
* retire_virtual_neg.cc -- retire() rejected when
hazard_pointer_obj_base is a virtual base; gated on
__cpp_lib_is_virtual_base_of.
* nodiscard_neg.cc -- discarding the return of
make_hazard_pointer(), empty(), protect(), or try_protect()
trips -Werror=unused-result.
All tests pass at -std=gnu++26 on x86_64-pc-linux-gnu.
libstdc++-v3/ChangeLog:
* testsuite/30_threads/hazard_pointer/concurrent.cc: New test.
* testsuite/30_threads/hazard_pointer/ctor.cc: New test.
* testsuite/30_threads/hazard_pointer/custom_deleter.cc: New test.
* testsuite/30_threads/hazard_pointer/empty.cc: New test.
* testsuite/30_threads/hazard_pointer/make_hazard_pointer.cc: New test.
* testsuite/30_threads/hazard_pointer/move_assign.cc: New test.
* testsuite/30_threads/hazard_pointer/nodiscard_neg.cc: New test.
* testsuite/30_threads/hazard_pointer/noexcept.cc: New test.
* testsuite/30_threads/hazard_pointer/protect.cc: New test.
* testsuite/30_threads/hazard_pointer/protect_neg.cc: New test.
* testsuite/30_threads/hazard_pointer/reset_protection.cc: New test.
* testsuite/30_threads/hazard_pointer/retire.cc: New test.
* testsuite/30_threads/hazard_pointer/retire_neg.cc: New test.
* testsuite/30_threads/hazard_pointer/retire_virtual_neg.cc: New test.
* testsuite/30_threads/hazard_pointer/swap.cc: New test.
* testsuite/30_threads/hazard_pointer/thread_exit.cc: New test.
* testsuite/30_threads/hazard_pointer/try_protect.cc: New test.
* testsuite/30_threads/hazard_pointer/type_constraints.cc: New test.
---
.../30_threads/hazard_pointer/concurrent.cc | 226 ++++++++++++++++++
.../30_threads/hazard_pointer/ctor.cc | 102 ++++++++
.../hazard_pointer/custom_deleter.cc | 126 ++++++++++
.../30_threads/hazard_pointer/empty.cc | 105 ++++++++
.../hazard_pointer/make_hazard_pointer.cc | 84 +++++++
.../30_threads/hazard_pointer/move_assign.cc | 91 +++++++
.../hazard_pointer/nodiscard_neg.cc | 58 +++++
.../30_threads/hazard_pointer/noexcept.cc | 56 +++++
.../30_threads/hazard_pointer/protect.cc | 96 ++++++++
.../30_threads/hazard_pointer/protect_neg.cc | 43 ++++
.../hazard_pointer/reset_protection.cc | 116 +++++++++
.../30_threads/hazard_pointer/retire.cc | 119 +++++++++
.../30_threads/hazard_pointer/retire_neg.cc | 39 +++
.../hazard_pointer/retire_virtual_neg.cc | 40 ++++
.../30_threads/hazard_pointer/swap.cc | 133 +++++++++++
.../30_threads/hazard_pointer/thread_exit.cc | 98 ++++++++
.../30_threads/hazard_pointer/try_protect.cc | 132 ++++++++++
.../hazard_pointer/type_constraints.cc | 56 +++++
18 files changed, 1720 insertions(+)
create mode 100644
libstdc++-v3/testsuite/30_threads/hazard_pointer/concurrent.cc
create mode 100644 libstdc++-v3/testsuite/30_threads/hazard_pointer/ctor.cc
create mode 100644
libstdc++-v3/testsuite/30_threads/hazard_pointer/custom_deleter.cc
create mode 100644 libstdc++-v3/testsuite/30_threads/hazard_pointer/empty.cc
create mode 100644
libstdc++-v3/testsuite/30_threads/hazard_pointer/make_hazard_pointer.cc
create mode 100644
libstdc++-v3/testsuite/30_threads/hazard_pointer/move_assign.cc
create mode 100644
libstdc++-v3/testsuite/30_threads/hazard_pointer/nodiscard_neg.cc
create mode 100644 libstdc++-v3/testsuite/30_threads/hazard_pointer/noexcept.cc
create mode 100644 libstdc++-v3/testsuite/30_threads/hazard_pointer/protect.cc
create mode 100644
libstdc++-v3/testsuite/30_threads/hazard_pointer/protect_neg.cc
create mode 100644
libstdc++-v3/testsuite/30_threads/hazard_pointer/reset_protection.cc
create mode 100644 libstdc++-v3/testsuite/30_threads/hazard_pointer/retire.cc
create mode 100644
libstdc++-v3/testsuite/30_threads/hazard_pointer/retire_neg.cc
create mode 100644
libstdc++-v3/testsuite/30_threads/hazard_pointer/retire_virtual_neg.cc
create mode 100644 libstdc++-v3/testsuite/30_threads/hazard_pointer/swap.cc
create mode 100644
libstdc++-v3/testsuite/30_threads/hazard_pointer/thread_exit.cc
create mode 100644
libstdc++-v3/testsuite/30_threads/hazard_pointer/try_protect.cc
create mode 100644
libstdc++-v3/testsuite/30_threads/hazard_pointer/type_constraints.cc
diff --git a/libstdc++-v3/testsuite/30_threads/hazard_pointer/concurrent.cc
b/libstdc++-v3/testsuite/30_threads/hazard_pointer/concurrent.cc
new file mode 100644
index 00000000000..097866eea7e
--- /dev/null
+++ b/libstdc++-v3/testsuite/30_threads/hazard_pointer/concurrent.cc
@@ -0,0 +1,226 @@
+// { dg-do run { target c++26 } }
+// { dg-additional-options "-pthread" { target pthread } }
+// { dg-require-gthreads "" }
+
+// Copyright (C) 2026 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library. This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3. If not see
+// <http://www.gnu.org/licenses/>.
+
+#include <hazard_pointer>
+#include <atomic>
+#include <chrono>
+#include <functional>
+#include <thread>
+#include <vector>
+#include <testsuite_hooks.h>
+
+namespace hpd = std::__hazard_pointer;
+
+namespace
+{
+ struct Node : std::hazard_pointer_obj_base<Node>
+ {
+ int value;
+ explicit Node(int v) : value(v) {}
+ };
+
+ void reader_fn(std::atomic<Node*>& shared, std::atomic<bool>& stop,
+ std::atomic<int>& errors)
+ {
+ auto hp = std::make_hazard_pointer();
+ while (!stop.load(std::memory_order_acquire))
+ {
+ const Node* const p = hp.protect(shared);
+ if (p && p->value < 0)
+ errors.fetch_add(1, std::memory_order_relaxed);
+ hp.reset_protection();
+ }
+ }
+
+ void writer_fn(std::atomic<Node*>& shared, std::atomic<bool>& stop)
+ {
+ int counter = 0;
+ while (!stop.load(std::memory_order_acquire))
+ {
+ Node* next = new Node(++counter);
+ Node* const old = shared.exchange(next, std::memory_order_seq_cst);
+ if (old)
+ old->retire();
+ }
+ Node* final_ = shared.exchange(nullptr, std::memory_order_seq_cst);
+ if (final_)
+ final_->retire();
+ }
+}
+
+// Readers and one writer: no data race, no error from reading freed memory.
+void test01()
+{
+ std::atomic<Node*> shared{new Node(1)};
+ std::atomic<bool> stop{false};
+ std::atomic<int> errors{0};
+
+ constexpr int kReaders = 4;
+ std::vector<std::thread> threads;
+ threads.reserve(kReaders + 1);
+
+ for (int i = 0; i < kReaders; ++i)
+ threads.emplace_back(reader_fn, std::ref(shared), std::ref(stop),
+ std::ref(errors));
+ threads.emplace_back(writer_fn, std::ref(shared), std::ref(stop));
+
+ std::this_thread::sleep_for(std::chrono::milliseconds(200));
+ stop.store(true, std::memory_order_release);
+ for (auto& t : threads)
+ t.join();
+
+ hpd::_S_default_domain()._M_synchronize();
+ VERIFY( errors.load() == 0 );
+}
+
+// protect() sees the latest value across stores.
+void test02()
+{
+ auto hp = std::make_hazard_pointer();
+ Node a{10}, b{20};
+ std::atomic<Node*> src{&a};
+
+ const Node* const p1 = hp.protect(src);
+ VERIFY( p1 == &a );
+
+ src.store(&b, std::memory_order_release);
+ const Node* const p2 = hp.protect(src);
+ VERIFY( p2 == &b );
+}
+
+// Multiple writers, multiple readers: no data race.
+void test03()
+{
+ constexpr int kReaders = 4;
+ constexpr int kWriters = 4;
+
+ std::vector<std::atomic<Node*>> srcs(kWriters);
+ for (auto& s : srcs)
+ s.store(new Node(1), std::memory_order_relaxed);
+
+ std::atomic<bool> stop{false};
+ std::atomic<int> errors{0};
+
+ auto multi_reader = [&] {
+ std::vector<std::hazard_pointer> hps;
+ hps.reserve(kWriters);
+ for (int i = 0; i < kWriters; ++i)
+ hps.emplace_back(std::make_hazard_pointer());
+ while (!stop.load(std::memory_order_acquire))
+ for (int i = 0; i < kWriters; ++i)
+ {
+ const Node* const p = hps[i].protect(srcs[i]);
+ if (p && p->value < 0)
+ errors.fetch_add(1, std::memory_order_relaxed);
+ hps[i].reset_protection();
+ }
+ };
+
+ auto single_writer = [&](int idx) {
+ int counter = 1;
+ while (!stop.load(std::memory_order_acquire))
+ {
+ Node* next = new Node(++counter);
+ Node* old = srcs[idx].exchange(next, std::memory_order_seq_cst);
+ if (old)
+ old->retire();
+ }
+ Node* final_ = srcs[idx].exchange(nullptr, std::memory_order_seq_cst);
+ if (final_)
+ final_->retire();
+ };
+
+ std::vector<std::thread> threads;
+ threads.reserve(kReaders + kWriters);
+ for (int i = 0; i < kReaders; ++i)
+ threads.emplace_back(multi_reader);
+ for (int i = 0; i < kWriters; ++i)
+ threads.emplace_back(single_writer, i);
+
+ std::this_thread::sleep_for(std::chrono::milliseconds(200));
+ stop.store(true, std::memory_order_release);
+ for (auto& t : threads)
+ t.join();
+
+ hpd::_S_default_domain()._M_synchronize();
+ VERIFY( errors.load() == 0 );
+}
+
+// Retire from many threads: all reclaimed by thread-exit drain.
+void test04()
+{
+ constexpr int kThreads = 8;
+ constexpr int kObjectsPerThread = 10;
+ std::atomic<int> total_deleted{0};
+
+ struct CountedNode : std::hazard_pointer_obj_base<CountedNode>
+ {
+ std::atomic<int>& counter;
+ explicit CountedNode(std::atomic<int>& c) : counter(c) {}
+ ~CountedNode() { counter.fetch_add(1, std::memory_order_relaxed); }
+ };
+
+ std::vector<std::thread> threads;
+ threads.reserve(kThreads);
+ for (int i = 0; i < kThreads; ++i)
+ threads.emplace_back([&] {
+ for (int j = 0; j < kObjectsPerThread; ++j)
+ (new CountedNode(total_deleted))->retire();
+ });
+ for (auto& t : threads)
+ t.join();
+
+ VERIFY( total_deleted.load() == kThreads * kObjectsPerThread );
+}
+
+// High contention: readers + writer over longer duration.
+void test05()
+{
+ constexpr int kReaders = 6;
+ std::atomic<Node*> shared{new Node(42)};
+ std::atomic<bool> stop{false};
+ std::atomic<int> errors{0};
+
+ std::vector<std::thread> threads;
+ threads.reserve(kReaders + 1);
+
+ for (int i = 0; i < kReaders; ++i)
+ threads.emplace_back(reader_fn, std::ref(shared), std::ref(stop),
+ std::ref(errors));
+ threads.emplace_back(writer_fn, std::ref(shared), std::ref(stop));
+
+ std::this_thread::sleep_for(std::chrono::milliseconds(300));
+ stop.store(true, std::memory_order_release);
+ for (auto& t : threads)
+ t.join();
+
+ hpd::_S_default_domain()._M_synchronize();
+ VERIFY( errors.load() == 0 );
+}
+
+int main()
+{
+ test01();
+ test02();
+ test03();
+ test04();
+ test05();
+}
diff --git a/libstdc++-v3/testsuite/30_threads/hazard_pointer/ctor.cc
b/libstdc++-v3/testsuite/30_threads/hazard_pointer/ctor.cc
new file mode 100644
index 00000000000..791b3cdbadc
--- /dev/null
+++ b/libstdc++-v3/testsuite/30_threads/hazard_pointer/ctor.cc
@@ -0,0 +1,102 @@
+// { dg-do run { target c++26 } }
+// { dg-additional-options "-pthread" { target pthread } }
+// { dg-require-gthreads "" }
+
+// Copyright (C) 2026 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library. This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3. If not see
+// <http://www.gnu.org/licenses/>.
+
+#include <hazard_pointer>
+#include <utility>
+#include <testsuite_hooks.h>
+
+namespace hpd = std::__hazard_pointer;
+
+// Default-constructed handle is empty.
+void test01()
+{
+ const std::hazard_pointer hp;
+ VERIFY( hp.empty() );
+}
+
+// Default-constructed handle does not increment active slot count.
+void test02()
+{
+ const auto before = hpd::_S_default_domain()._M_active_slots();
+ const std::hazard_pointer hp;
+ VERIFY( hpd::_S_default_domain()._M_active_slots() == before );
+}
+
+// Move ctor transfers slot ownership: count unchanged.
+void test03()
+{
+ const auto before = hpd::_S_default_domain()._M_active_slots();
+ auto a = std::make_hazard_pointer();
+ VERIFY( hpd::_S_default_domain()._M_active_slots() == before + 1 );
+ const std::hazard_pointer b = std::move(a);
+ VERIFY( hpd::_S_default_domain()._M_active_slots() == before + 1 );
+}
+
+// Move ctor: source becomes empty.
+void test04()
+{
+ auto a = std::make_hazard_pointer();
+ const std::hazard_pointer b = std::move(a);
+ VERIFY( a.empty() );
+ VERIFY( !b.empty() );
+}
+
+// Destructor releases slot.
+void test05()
+{
+ const auto before = hpd::_S_default_domain()._M_active_slots();
+ {
+ auto hp = std::make_hazard_pointer();
+ VERIFY( hpd::_S_default_domain()._M_active_slots() == before + 1 );
+ }
+ VERIFY( hpd::_S_default_domain()._M_active_slots() == before );
+}
+
+// Destructor on default-constructed handle is a no-op.
+void test06()
+{
+ const auto before = hpd::_S_default_domain()._M_active_slots();
+ {
+ const std::hazard_pointer hp;
+ }
+ VERIFY( hpd::_S_default_domain()._M_active_slots() == before );
+}
+
+// Multiple hazard_pointers are distinct slots.
+void test07()
+{
+ const auto before = hpd::_S_default_domain()._M_active_slots();
+ auto a = std::make_hazard_pointer();
+ auto b = std::make_hazard_pointer();
+ auto c = std::make_hazard_pointer();
+ VERIFY( hpd::_S_default_domain()._M_active_slots() == before + 3 );
+}
+
+int main()
+{
+ test01();
+ test02();
+ test03();
+ test04();
+ test05();
+ test06();
+ test07();
+}
diff --git a/libstdc++-v3/testsuite/30_threads/hazard_pointer/custom_deleter.cc
b/libstdc++-v3/testsuite/30_threads/hazard_pointer/custom_deleter.cc
new file mode 100644
index 00000000000..36d143945a6
--- /dev/null
+++ b/libstdc++-v3/testsuite/30_threads/hazard_pointer/custom_deleter.cc
@@ -0,0 +1,126 @@
+// { dg-do run { target c++26 } }
+// { dg-additional-options "-pthread" { target pthread } }
+// { dg-require-gthreads "" }
+
+// Copyright (C) 2026 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library. This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3. If not see
+// <http://www.gnu.org/licenses/>.
+
+#include <hazard_pointer>
+#include <atomic>
+#include <testsuite_hooks.h>
+
+namespace hpd = std::__hazard_pointer;
+
+namespace
+{
+ struct LoggingNode;
+
+ struct LoggingDeleter
+ {
+ int* delete_count = nullptr;
+ LoggingDeleter() = default;
+ explicit LoggingDeleter(int* c) : delete_count(c) {}
+ void operator()(LoggingNode* p) const;
+ };
+
+ struct LoggingNode : std::hazard_pointer_obj_base<LoggingNode,
LoggingDeleter> {};
+
+ inline void LoggingDeleter::operator()(LoggingNode* p) const
+ {
+ if (delete_count)
+ ++(*delete_count);
+ delete p;
+ }
+
+ struct TrackedNode : std::hazard_pointer_obj_base<TrackedNode>
+ {
+ explicit TrackedNode(int& c) : counter(c) {}
+ ~TrackedNode() { ++counter; }
+ int& counter;
+ };
+}
+
+// Default deleter calls delete.
+void test01()
+{
+ int dtor_count = 0;
+ auto* node = new TrackedNode(dtor_count);
+ node->retire();
+ hpd::_S_default_domain()._M_synchronize();
+ VERIFY( dtor_count == 1 );
+}
+
+// Functor deleter is invoked.
+void test02()
+{
+ int delete_count = 0;
+ auto* node = new LoggingNode();
+ node->retire(LoggingDeleter{&delete_count});
+ hpd::_S_default_domain()._M_synchronize();
+ VERIFY( delete_count == 1 );
+}
+
+// Functor deleter not called while protected.
+void test03()
+{
+ int delete_count = 0;
+ auto* node = new LoggingNode();
+ const std::atomic<LoggingNode*> src{node};
+
+ auto hp = std::make_hazard_pointer();
+ (void)hp.protect(src);
+ node->retire(LoggingDeleter{&delete_count});
+ hpd::_S_default_domain()._M_synchronize();
+ VERIFY( delete_count == 0 );
+
+ hp.reset_protection();
+ hpd::_S_default_domain()._M_synchronize();
+ VERIFY( delete_count == 1 );
+}
+
+// retire() with no arg uses default-constructed D.
+void test04()
+{
+ int dtor_count = 0;
+ auto* node = new TrackedNode(dtor_count);
+ node->retire();
+ hpd::_S_default_domain()._M_synchronize();
+ VERIFY( dtor_count == 1 );
+}
+
+// Multiple nodes with custom deleter all invoked.
+void test05()
+{
+ constexpr int kCount = 5;
+ int delete_count = 0;
+ for (int i = 0; i < kCount; ++i)
+ {
+ auto* node = new LoggingNode();
+ node->retire(LoggingDeleter{&delete_count});
+ }
+ hpd::_S_default_domain()._M_synchronize();
+ VERIFY( delete_count == kCount );
+}
+
+int main()
+{
+ test01();
+ test02();
+ test03();
+ test04();
+ test05();
+}
diff --git a/libstdc++-v3/testsuite/30_threads/hazard_pointer/empty.cc
b/libstdc++-v3/testsuite/30_threads/hazard_pointer/empty.cc
new file mode 100644
index 00000000000..afd53c8bd8a
--- /dev/null
+++ b/libstdc++-v3/testsuite/30_threads/hazard_pointer/empty.cc
@@ -0,0 +1,105 @@
+// { dg-do run { target c++26 } }
+// { dg-additional-options "-pthread" { target pthread } }
+// { dg-require-gthreads "" }
+
+// Copyright (C) 2026 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library. This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3. If not see
+// <http://www.gnu.org/licenses/>.
+
+#include <hazard_pointer>
+#include <atomic>
+#include <utility>
+#include <testsuite_hooks.h>
+
+namespace
+{
+ struct Node : std::hazard_pointer_obj_base<Node> {};
+}
+
+void test01()
+{
+ const std::hazard_pointer hp;
+ VERIFY( hp.empty() );
+}
+
+void test02()
+{
+ auto hp = std::make_hazard_pointer();
+ VERIFY( !hp.empty() );
+}
+
+// After move ctor: source empty, dest non-empty.
+void test03()
+{
+ auto a = std::make_hazard_pointer();
+ const std::hazard_pointer b = std::move(a);
+ VERIFY( a.empty() );
+ VERIFY( !b.empty() );
+}
+
+// After move-assign: source empty.
+void test04()
+{
+ auto a = std::make_hazard_pointer();
+ std::hazard_pointer b;
+ b = std::move(a);
+ VERIFY( a.empty() );
+ VERIFY( !b.empty() );
+}
+
+// After protect(): still owns slot.
+void test05()
+{
+ auto hp = std::make_hazard_pointer();
+ Node x;
+ const std::atomic<Node*> src{&x};
+ (void)hp.protect(src);
+ VERIFY( !hp.empty() );
+}
+
+// After reset_protection(): hazard cleared, slot kept.
+void test06()
+{
+ auto hp = std::make_hazard_pointer();
+ Node x;
+ const std::atomic<Node*> src{&x};
+ (void)hp.protect(src);
+ hp.reset_protection();
+ VERIFY( !hp.empty() );
+}
+
+// After swap: ownership exchanged.
+void test07()
+{
+ auto a = std::make_hazard_pointer();
+ std::hazard_pointer b;
+ VERIFY( !a.empty() );
+ VERIFY( b.empty() );
+ a.swap(b);
+ VERIFY( a.empty() );
+ VERIFY( !b.empty() );
+}
+
+int main()
+{
+ test01();
+ test02();
+ test03();
+ test04();
+ test05();
+ test06();
+ test07();
+}
diff --git
a/libstdc++-v3/testsuite/30_threads/hazard_pointer/make_hazard_pointer.cc
b/libstdc++-v3/testsuite/30_threads/hazard_pointer/make_hazard_pointer.cc
new file mode 100644
index 00000000000..a726480dc63
--- /dev/null
+++ b/libstdc++-v3/testsuite/30_threads/hazard_pointer/make_hazard_pointer.cc
@@ -0,0 +1,84 @@
+// { dg-do run { target c++26 } }
+// { dg-additional-options "-pthread" { target pthread } }
+// { dg-require-gthreads "" }
+
+// Copyright (C) 2026 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library. This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3. If not see
+// <http://www.gnu.org/licenses/>.
+
+#include <hazard_pointer>
+#include <cstddef>
+#include <vector>
+#include <testsuite_hooks.h>
+
+namespace hpd = std::__hazard_pointer;
+
+// Returns non-empty handle.
+void test01()
+{
+ auto hp = std::make_hazard_pointer();
+ VERIFY( !hp.empty() );
+}
+
+// Increments active slot count by 1.
+void test02()
+{
+ const auto before = hpd::_S_default_domain()._M_active_slots();
+ auto hp = std::make_hazard_pointer();
+ VERIFY( hpd::_S_default_domain()._M_active_slots() == before + 1 );
+}
+
+// Multiple calls return distinct slots.
+void test03()
+{
+ const auto before = hpd::_S_default_domain()._M_active_slots();
+ auto a = std::make_hazard_pointer();
+ auto b = std::make_hazard_pointer();
+ auto c = std::make_hazard_pointer();
+ VERIFY( hpd::_S_default_domain()._M_active_slots() == before + 3 );
+}
+
+// Slot reused after handle destruction.
+void test04()
+{
+ const auto before = hpd::_S_default_domain()._M_active_slots();
+ {
+ auto hp = std::make_hazard_pointer();
+ }
+ auto hp2 = std::make_hazard_pointer();
+ VERIFY( hpd::_S_default_domain()._M_active_slots() == before + 1 );
+}
+
+// Pool grows beyond initial capacity (_S_initial_slots == 8).
+void test05()
+{
+ const auto before = hpd::_S_default_domain()._M_active_slots();
+ constexpr std::size_t kExtra = 8 + 4;
+ std::vector<std::hazard_pointer> hps;
+ hps.reserve(kExtra);
+ for (std::size_t i = 0; i < kExtra; ++i)
+ hps.emplace_back(std::make_hazard_pointer());
+ VERIFY( hpd::_S_default_domain()._M_active_slots() == before + kExtra );
+}
+
+int main()
+{
+ test01();
+ test02();
+ test03();
+ test04();
+ test05();
+}
\ No newline at end of file
diff --git a/libstdc++-v3/testsuite/30_threads/hazard_pointer/move_assign.cc
b/libstdc++-v3/testsuite/30_threads/hazard_pointer/move_assign.cc
new file mode 100644
index 00000000000..7f4e40c1cda
--- /dev/null
+++ b/libstdc++-v3/testsuite/30_threads/hazard_pointer/move_assign.cc
@@ -0,0 +1,91 @@
+// { dg-do run { target c++26 } }
+// { dg-additional-options "-pthread" { target pthread } }
+// { dg-require-gthreads "" }
+
+// Copyright (C) 2026 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library. This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3. If not see
+// <http://www.gnu.org/licenses/>.
+
+#include <hazard_pointer>
+#include <utility>
+#include <testsuite_hooks.h>
+
+namespace hpd = std::__hazard_pointer;
+
+// Transfers slot ownership.
+void test01()
+{
+ const auto before = hpd::_S_default_domain()._M_active_slots();
+ auto a = std::make_hazard_pointer();
+ std::hazard_pointer b;
+ VERIFY( !a.empty() );
+ VERIFY( b.empty() );
+ b = std::move(a);
+ VERIFY( a.empty() );
+ VERIFY( !b.empty() );
+ VERIFY( hpd::_S_default_domain()._M_active_slots() == before + 1 );
+}
+
+// Source becomes empty after move-assign.
+void test02()
+{
+ auto a = std::make_hazard_pointer();
+ std::hazard_pointer b;
+ b = std::move(a);
+ VERIFY( a.empty() );
+}
+
+// Destination releases prior slot.
+void test03()
+{
+ const auto before = hpd::_S_default_domain()._M_active_slots();
+ auto a = std::make_hazard_pointer();
+ auto b = std::make_hazard_pointer();
+ VERIFY( hpd::_S_default_domain()._M_active_slots() == before + 2 );
+ b = std::move(a);
+ VERIFY( hpd::_S_default_domain()._M_active_slots() == before + 1 );
+}
+
+// Self-assignment is a no-op.
+void test04()
+{
+ auto hp = std::make_hazard_pointer();
+ const auto before = hpd::_S_default_domain()._M_active_slots();
+ auto& alias = hp;
+ hp = std::move(alias);
+ VERIFY( hpd::_S_default_domain()._M_active_slots() == before );
+ VERIFY( !hp.empty() );
+}
+
+// Move-assign empty to empty: both stay empty, no slot change.
+void test05()
+{
+ const auto before = hpd::_S_default_domain()._M_active_slots();
+ std::hazard_pointer a;
+ std::hazard_pointer b;
+ b = std::move(a);
+ VERIFY( b.empty() );
+ VERIFY( hpd::_S_default_domain()._M_active_slots() == before );
+}
+
+int main()
+{
+ test01();
+ test02();
+ test03();
+ test04();
+ test05();
+}
diff --git a/libstdc++-v3/testsuite/30_threads/hazard_pointer/nodiscard_neg.cc
b/libstdc++-v3/testsuite/30_threads/hazard_pointer/nodiscard_neg.cc
new file mode 100644
index 00000000000..eb725da6790
--- /dev/null
+++ b/libstdc++-v3/testsuite/30_threads/hazard_pointer/nodiscard_neg.cc
@@ -0,0 +1,58 @@
+// { dg-do compile { target c++26 } }
+// { dg-additional-options "-pthread -Werror=unused-result" { target pthread }
}
+// { dg-require-gthreads "" }
+
+// Copyright (C) 2026 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library. This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3. If not see
+// <http://www.gnu.org/licenses/>.
+
+// Negative tests: each [[nodiscard]] return discard must trip
+// -Werror=unused-result.
+
+#include <hazard_pointer>
+#include <atomic>
+
+struct Node : std::hazard_pointer_obj_base<Node> {};
+
+void test_make_hazard_pointer()
+{
+ std::make_hazard_pointer(); // { dg-error "ignoring return value" }
+}
+
+void test_empty()
+{
+ auto hp = std::make_hazard_pointer();
+ hp.empty(); // { dg-error "ignoring return value" }
+}
+
+void test_protect()
+{
+ auto hp = std::make_hazard_pointer();
+ Node x;
+ std::atomic<Node*> src{&x};
+ hp.protect(src); // { dg-error "ignoring return value" }
+}
+
+void test_try_protect()
+{
+ auto hp = std::make_hazard_pointer();
+ Node x;
+ std::atomic<Node*> src{&x};
+ Node* ptr = &x;
+ hp.try_protect(ptr, src); // { dg-error "ignoring return value" }
+}
+
+// { dg-prune-output "some warnings being treated as errors" }
diff --git a/libstdc++-v3/testsuite/30_threads/hazard_pointer/noexcept.cc
b/libstdc++-v3/testsuite/30_threads/hazard_pointer/noexcept.cc
new file mode 100644
index 00000000000..cc19bc309cd
--- /dev/null
+++ b/libstdc++-v3/testsuite/30_threads/hazard_pointer/noexcept.cc
@@ -0,0 +1,56 @@
+// { dg-do compile { target c++26 } }
+// { dg-additional-options "-pthread" { target pthread } }
+// { dg-require-gthreads "" }
+
+// Copyright (C) 2026 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library. This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3. If not see
+// <http://www.gnu.org/licenses/>.
+
+#include <hazard_pointer>
+#include <atomic>
+#include <type_traits>
+#include <utility>
+
+namespace
+{
+ struct Node : std::hazard_pointer_obj_base<Node> {};
+ using HP = std::hazard_pointer;
+
+ static_assert(std::is_nothrow_default_constructible_v<HP>);
+ static_assert(std::is_nothrow_move_constructible_v<HP>);
+ static_assert(std::is_nothrow_move_assignable_v<HP>);
+
+ static_assert(noexcept(std::declval<const HP&>().empty()));
+
+ static_assert(noexcept(std::declval<HP&>().protect(
+ std::declval<std::atomic<Node*>&>())));
+
+ static_assert(noexcept(std::declval<HP&>().try_protect(
+ std::declval<Node*&>(),
+ std::declval<const std::atomic<Node*>&>())));
+
+ static_assert(noexcept(std::declval<HP&>().reset_protection()));
+ static_assert(noexcept(std::declval<HP&>().reset_protection(nullptr)));
+
+ static_assert(noexcept(std::declval<HP&>().reset_protection(
+ std::declval<const Node*>())));
+
+ static_assert(noexcept(std::declval<HP&>().swap(std::declval<HP&>())));
+
+ static_assert(noexcept(std::swap(std::declval<HP&>(), std::declval<HP&>())));
+
+ static_assert(noexcept(std::declval<Node&>().retire()));
+}
diff --git a/libstdc++-v3/testsuite/30_threads/hazard_pointer/protect.cc
b/libstdc++-v3/testsuite/30_threads/hazard_pointer/protect.cc
new file mode 100644
index 00000000000..19f38292263
--- /dev/null
+++ b/libstdc++-v3/testsuite/30_threads/hazard_pointer/protect.cc
@@ -0,0 +1,96 @@
+// { dg-do run { target c++26 } }
+// { dg-additional-options "-pthread" { target pthread } }
+// { dg-require-gthreads "" }
+
+// Copyright (C) 2026 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library. This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3. If not see
+// <http://www.gnu.org/licenses/>.
+
+#include <hazard_pointer>
+#include <atomic>
+#include <testsuite_hooks.h>
+
+namespace
+{
+ struct Node : std::hazard_pointer_obj_base<Node>
+ {
+ int value = 0;
+ };
+}
+
+// Returns current value of the atomic.
+void test01()
+{
+ auto hp = std::make_hazard_pointer();
+ Node x;
+ x.value = 42;
+ const std::atomic<Node*> src{&x};
+ const Node* const p = hp.protect(src);
+ VERIFY( p == &x );
+ VERIFY( p->value == 42 );
+}
+
+// Returns nullptr for null atomic.
+void test02()
+{
+ auto hp = std::make_hazard_pointer();
+ const std::atomic<Node*> src{nullptr};
+ VERIFY( hp.protect(src) == nullptr );
+}
+
+// Slot not empty after protect.
+void test03()
+{
+ auto hp = std::make_hazard_pointer();
+ Node x;
+ const std::atomic<Node*> src{&x};
+ (void)hp.protect(src);
+ VERIFY( !hp.empty() );
+}
+
+// protect() matches atomic load.
+void test04()
+{
+ auto hp = std::make_hazard_pointer();
+ Node vals[3];
+ vals[0].value = 1;
+ vals[1].value = 2;
+ vals[2].value = 3;
+ const std::atomic<Node*> src{&vals[0]};
+ const Node* const p = hp.protect(src);
+ VERIFY( p == src.load() );
+}
+
+// Re-protect on the same handle updates the slot.
+void test05()
+{
+ auto hp = std::make_hazard_pointer();
+ Node a, b;
+ std::atomic<Node*> src{&a};
+ (void)hp.protect(src);
+ src.store(&b, std::memory_order_relaxed);
+ const Node* const p = hp.protect(src);
+ VERIFY( p == &b );
+}
+
+int main()
+{
+ test01();
+ test02();
+ test03();
+ test04();
+ test05();
+}
diff --git a/libstdc++-v3/testsuite/30_threads/hazard_pointer/protect_neg.cc
b/libstdc++-v3/testsuite/30_threads/hazard_pointer/protect_neg.cc
new file mode 100644
index 00000000000..07cd3f66d6b
--- /dev/null
+++ b/libstdc++-v3/testsuite/30_threads/hazard_pointer/protect_neg.cc
@@ -0,0 +1,43 @@
+// { dg-do compile { target c++26 } }
+// { dg-additional-options "-pthread" { target pthread } }
+// { dg-require-gthreads "" }
+
+// Copyright (C) 2026 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library. This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3. If not see
+// <http://www.gnu.org/licenses/>.
+
+// Negative tests: protect<T>() must be rejected when T is not _Protectable.
+
+#include <hazard_pointer>
+#include <atomic>
+
+struct Plain {};
+
+void test_int()
+{
+ auto hp = std::make_hazard_pointer();
+ std::atomic<int*> src{nullptr};
+ (void)hp.protect(src); // int is not _Protectable
+}
+
+void test_nonhazard_class()
+{
+ auto hp = std::make_hazard_pointer();
+ std::atomic<Plain*> src{nullptr};
+ (void)hp.protect(src); // Plain has no _Obj_base_tag base
+}
+
+// { dg-error "hazard-protectable type" "" { target *-*-* } 0 }
diff --git
a/libstdc++-v3/testsuite/30_threads/hazard_pointer/reset_protection.cc
b/libstdc++-v3/testsuite/30_threads/hazard_pointer/reset_protection.cc
new file mode 100644
index 00000000000..5dddef71611
--- /dev/null
+++ b/libstdc++-v3/testsuite/30_threads/hazard_pointer/reset_protection.cc
@@ -0,0 +1,116 @@
+// { dg-do run { target c++26 } }
+// { dg-additional-options "-pthread" { target pthread } }
+// { dg-require-gthreads "" }
+
+// Copyright (C) 2026 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library. This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3. If not see
+// <http://www.gnu.org/licenses/>.
+
+#include <hazard_pointer>
+#include <atomic>
+#include <testsuite_hooks.h>
+
+namespace hpd = std::__hazard_pointer;
+
+namespace
+{
+ struct Node : std::hazard_pointer_obj_base<Node> {};
+
+ struct Tracked : std::hazard_pointer_obj_base<Tracked>
+ {
+ explicit Tracked(int& c) : counter(c) {}
+ ~Tracked() { ++counter; }
+ int& counter;
+ };
+}
+
+// reset_protection clears hazard, slot still owned.
+void test01()
+{
+ auto hp = std::make_hazard_pointer();
+ Node x;
+ const std::atomic<Node*> src{&x};
+ (void)hp.protect(src);
+ hp.reset_protection();
+ VERIFY( !hp.empty() );
+}
+
+// nullptr overload clears hazard.
+void test02()
+{
+ auto hp = std::make_hazard_pointer();
+ Node x;
+ const std::atomic<Node*> src{&x};
+ (void)hp.protect(src);
+ hp.reset_protection(nullptr);
+ VERIFY( !hp.empty() );
+}
+
+// After reset, object can be reclaimed.
+void test03()
+{
+ auto hp = std::make_hazard_pointer();
+ int dtor_count = 0;
+ auto* obj = new Tracked(dtor_count);
+ const std::atomic<Tracked*> src{obj};
+ (void)hp.protect(src);
+
+ obj->retire();
+ hpd::_S_default_domain()._M_synchronize();
+ VERIFY( dtor_count == 0 );
+
+ hp.reset_protection();
+ hpd::_S_default_domain()._M_synchronize();
+ VERIFY( dtor_count == 1 );
+}
+
+// Typed-pointer overload publishes a hazard.
+void test04()
+{
+ auto hp = std::make_hazard_pointer();
+ int dtor = 0;
+ auto* obj = new Tracked(dtor);
+ hp.reset_protection(obj);
+
+ obj->retire();
+ hpd::_S_default_domain()._M_synchronize();
+ VERIFY( dtor == 0 );
+
+ hp.reset_protection();
+ hpd::_S_default_domain()._M_synchronize();
+ VERIFY( dtor == 1 );
+}
+
+// Double reset is safe (idempotent).
+void test05()
+{
+ auto hp = std::make_hazard_pointer();
+ Node x;
+ const std::atomic<Node*> src{&x};
+ (void)hp.protect(src);
+ hp.reset_protection();
+ hp.reset_protection();
+ VERIFY( !hp.empty() );
+}
+
+int main()
+{
+ test01();
+ test02();
+ test03();
+ test04();
+ test05();
+}
diff --git a/libstdc++-v3/testsuite/30_threads/hazard_pointer/retire.cc
b/libstdc++-v3/testsuite/30_threads/hazard_pointer/retire.cc
new file mode 100644
index 00000000000..afcc97f2ca2
--- /dev/null
+++ b/libstdc++-v3/testsuite/30_threads/hazard_pointer/retire.cc
@@ -0,0 +1,119 @@
+// { dg-do run { target c++26 } }
+// { dg-additional-options "-pthread" { target pthread } }
+// { dg-require-gthreads "" }
+
+// Copyright (C) 2026 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library. This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3. If not see
+// <http://www.gnu.org/licenses/>.
+
+#include <hazard_pointer>
+#include <atomic>
+#include <testsuite_hooks.h>
+
+namespace hpd = std::__hazard_pointer;
+
+namespace
+{
+ struct Tracked : std::hazard_pointer_obj_base<Tracked>
+ {
+ explicit Tracked(int& counter) : counter_(counter) {}
+ ~Tracked() { ++counter_; }
+ int& counter_;
+ };
+}
+
+// Retired object deleted after synchronize.
+void test01()
+{
+ // Hold one active slot so the auto-sync threshold (2 * active_count) >= 2,
+ // ensuring the single retire below does NOT auto-sync.
+ auto hp = std::make_hazard_pointer();
+ int dtor_count = 0;
+ (new Tracked(dtor_count))->retire();
+ VERIFY( dtor_count == 0 );
+ hpd::_S_default_domain()._M_synchronize();
+ VERIFY( dtor_count == 1 );
+}
+
+// Multiple retired objects all deleted on synchronize.
+void test02()
+{
+ int dtor_count = 0;
+ for (int i = 0; i < 5; ++i)
+ (new Tracked(dtor_count))->retire();
+ hpd::_S_default_domain()._M_synchronize();
+ VERIFY( dtor_count == 5 );
+}
+
+// Protected object survives synchronize.
+void test03()
+{
+ int dtor_count = 0;
+ auto* obj = new Tracked(dtor_count);
+ const std::atomic<Tracked*> src{obj};
+
+ auto hp = std::make_hazard_pointer();
+ const Tracked* const protected_obj = hp.protect(src);
+ VERIFY( protected_obj == obj );
+
+ obj->retire();
+ hpd::_S_default_domain()._M_synchronize();
+ VERIFY( dtor_count == 0 );
+
+ hp.reset_protection();
+ hpd::_S_default_domain()._M_synchronize();
+ VERIFY( dtor_count == 1 );
+}
+
+// Retire list grows before synchronize.
+void test04()
+{
+ auto hp = std::make_hazard_pointer();
+ const auto before = hpd::_S_default_domain()._M_retire_list_size();
+ int dtor_count = 0;
+ (new Tracked(dtor_count))->retire();
+ (new Tracked(dtor_count))->retire();
+ VERIFY( hpd::_S_default_domain()._M_retire_list_size() == before + 2 );
+ hpd::_S_default_domain()._M_synchronize();
+ VERIFY( hpd::_S_default_domain()._M_retire_list_size() == 0u );
+ VERIFY( dtor_count == 2 );
+}
+
+// Threshold auto-triggers synchronize: 1 active slot -> threshold 2 -> 3
retires trip it.
+void test05()
+{
+ auto hp = std::make_hazard_pointer();
+ int dummy = 0;
+ Tracked x{dummy};
+ const std::atomic<Tracked*> src{&x};
+ const Tracked* const protected_x = hp.protect(src);
+ VERIFY( protected_x == &x );
+
+ int dtor_count = 0;
+ for (int i = 0; i < 3; ++i)
+ (new Tracked(dtor_count))->retire();
+
+ VERIFY( dtor_count > 0 );
+}
+
+int main()
+{
+ test01();
+ test02();
+ test03();
+ test04();
+ test05();
+}
diff --git a/libstdc++-v3/testsuite/30_threads/hazard_pointer/retire_neg.cc
b/libstdc++-v3/testsuite/30_threads/hazard_pointer/retire_neg.cc
new file mode 100644
index 00000000000..9b1c952f3af
--- /dev/null
+++ b/libstdc++-v3/testsuite/30_threads/hazard_pointer/retire_neg.cc
@@ -0,0 +1,39 @@
+// { dg-do compile { target c++26 } }
+// { dg-additional-options "-pthread" { target pthread } }
+// { dg-require-gthreads "" }
+
+// Copyright (C) 2026 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library. This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3. If not see
+// <http://www.gnu.org/licenses/>.
+
+// Negative test: retire() must be rejected when hazard_pointer_obj_base
+// is a private base of T (is_convertible_v<T*, base*> fails).
+
+#include <hazard_pointer>
+
+struct Node : private std::hazard_pointer_obj_base<Node>
+{
+ using hazard_pointer_obj_base::retire;
+};
+
+void test()
+{
+ auto* p = new Node();
+ p->retire();
+}
+
+// { dg-error "must be a public base of T" "" { target *-*-* } 0 }
+// { dg-prune-output "inaccessible base of" }
diff --git
a/libstdc++-v3/testsuite/30_threads/hazard_pointer/retire_virtual_neg.cc
b/libstdc++-v3/testsuite/30_threads/hazard_pointer/retire_virtual_neg.cc
new file mode 100644
index 00000000000..7275a7b3f10
--- /dev/null
+++ b/libstdc++-v3/testsuite/30_threads/hazard_pointer/retire_virtual_neg.cc
@@ -0,0 +1,40 @@
+// { dg-do compile { target c++26 } }
+// { dg-additional-options "-pthread" { target pthread } }
+// { dg-require-gthreads "" }
+
+// Copyright (C) 2026 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library. This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3. If not see
+// <http://www.gnu.org/licenses/>.
+
+// Negative test: retire() must be rejected when hazard_pointer_obj_base
+// is a virtual base of T. Requires C++26 std::is_virtual_base_of.
+
+#include <hazard_pointer>
+
+#ifndef __cpp_lib_is_virtual_base_of
+# error "Requires __cpp_lib_is_virtual_base_of for this negative check"
+#endif
+
+struct Node : virtual std::hazard_pointer_obj_base<Node> {};
+
+void test()
+{
+ auto* p = new Node();
+ p->retire();
+}
+
+// { dg-error "must be a non-virtual base" "" { target *-*-* } 0 }
+// { dg-prune-output "cannot convert from pointer to base class" }
diff --git a/libstdc++-v3/testsuite/30_threads/hazard_pointer/swap.cc
b/libstdc++-v3/testsuite/30_threads/hazard_pointer/swap.cc
new file mode 100644
index 00000000000..2d516d0bfc7
--- /dev/null
+++ b/libstdc++-v3/testsuite/30_threads/hazard_pointer/swap.cc
@@ -0,0 +1,133 @@
+// { dg-do run { target c++26 } }
+// { dg-additional-options "-pthread" { target pthread } }
+// { dg-require-gthreads "" }
+
+// Copyright (C) 2026 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library. This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3. If not see
+// <http://www.gnu.org/licenses/>.
+
+#include <hazard_pointer>
+#include <atomic>
+#include <testsuite_hooks.h>
+
+namespace hpd = std::__hazard_pointer;
+
+namespace
+{
+ struct Tracked : std::hazard_pointer_obj_base<Tracked>
+ {
+ explicit Tracked(int& c) : counter(c) {}
+ ~Tracked() { ++counter; }
+ int& counter;
+ };
+}
+
+// Member swap exchanges non-empty and empty.
+void test01()
+{
+ auto a = std::make_hazard_pointer();
+ std::hazard_pointer b;
+ VERIFY( !a.empty() );
+ VERIFY( b.empty() );
+ a.swap(b);
+ VERIFY( a.empty() );
+ VERIFY( !b.empty() );
+}
+
+// Member swap both non-empty: slot count unchanged.
+void test02()
+{
+ const auto before = hpd::_S_default_domain()._M_active_slots();
+ auto a = std::make_hazard_pointer();
+ auto b = std::make_hazard_pointer();
+ VERIFY( hpd::_S_default_domain()._M_active_slots() == before + 2 );
+ a.swap(b);
+ VERIFY( hpd::_S_default_domain()._M_active_slots() == before + 2 );
+}
+
+// After swap, the protecting slot moves to the other handle.
+void test03()
+{
+ auto hp1 = std::make_hazard_pointer();
+ auto hp2 = std::make_hazard_pointer();
+
+ int dtor = 0;
+ auto* obj = new Tracked(dtor);
+ const std::atomic<Tracked*> src{obj};
+ (void)hp1.protect(src);
+
+ hp1.swap(hp2);
+
+ obj->retire();
+ hpd::_S_default_domain()._M_synchronize();
+ VERIFY( dtor == 0 );
+
+ hp2.reset_protection();
+ hpd::_S_default_domain()._M_synchronize();
+ VERIFY( dtor == 1 );
+}
+
+// Free swap exchanges slots.
+void test04()
+{
+ auto a = std::make_hazard_pointer();
+ std::hazard_pointer b;
+ VERIFY( !a.empty() );
+ VERIFY( b.empty() );
+ std::swap(a, b);
+ VERIFY( a.empty() );
+ VERIFY( !b.empty() );
+}
+
+// Free swap both non-empty: count unchanged.
+void test05()
+{
+ const auto before = hpd::_S_default_domain()._M_active_slots();
+ auto a = std::make_hazard_pointer();
+ auto b = std::make_hazard_pointer();
+ std::swap(a, b);
+ VERIFY( hpd::_S_default_domain()._M_active_slots() == before + 2 );
+}
+
+// Member self-swap is a no-op.
+void test06()
+{
+ auto hp = std::make_hazard_pointer();
+ VERIFY( !hp.empty() );
+ hp.swap(hp);
+ VERIFY( !hp.empty() );
+}
+
+// Free swap both empty: remains empty.
+void test07()
+{
+ std::hazard_pointer a;
+ std::hazard_pointer b;
+ std::swap(a, b);
+ VERIFY( a.empty() );
+ VERIFY( b.empty() );
+}
+
+int main()
+{
+ test01();
+ test02();
+ test03();
+ test04();
+ test05();
+ test06();
+ test07();
+}
diff --git a/libstdc++-v3/testsuite/30_threads/hazard_pointer/thread_exit.cc
b/libstdc++-v3/testsuite/30_threads/hazard_pointer/thread_exit.cc
new file mode 100644
index 00000000000..cabf95eaeef
--- /dev/null
+++ b/libstdc++-v3/testsuite/30_threads/hazard_pointer/thread_exit.cc
@@ -0,0 +1,98 @@
+// { dg-do run { target c++26 } }
+// { dg-additional-options "-pthread" { target pthread } }
+// { dg-require-gthreads "" }
+
+// Copyright (C) 2026 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library. This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3. If not see
+// <http://www.gnu.org/licenses/>.
+
+#include <hazard_pointer>
+#include <atomic>
+#include <thread>
+#include <testsuite_hooks.h>
+
+namespace hpd = std::__hazard_pointer;
+
+namespace
+{
+ struct Tracked : std::hazard_pointer_obj_base<Tracked>
+ {
+ explicit Tracked(int& c) : counter(c) {}
+ ~Tracked() { ++counter; }
+ int& counter;
+ };
+}
+
+// Retired objects in a worker thread reclaimed on thread exit.
+void test01()
+{
+ int dtor_count = 0;
+ std::thread t([&] {
+ for (int i = 0; i < 3; ++i)
+ (new Tracked(dtor_count))->retire();
+ });
+ t.join();
+ VERIFY( dtor_count == 3 );
+}
+
+// Protected survivors offloaded to orphan list, reclaimed by later
synchronize.
+void test02()
+{
+ int dtor_count = 0;
+ auto* obj = new Tracked(dtor_count);
+ const std::atomic<Tracked*> src{obj};
+
+ auto hp = std::make_hazard_pointer();
+ (void)hp.protect(src);
+
+ std::thread t([&] {
+ obj->retire();
+ });
+ t.join();
+
+ VERIFY( dtor_count == 0 );
+
+ hp.reset_protection();
+ hpd::_S_default_domain()._M_synchronize();
+ VERIFY( dtor_count == 1 );
+}
+
+// Many threads each drain own retire list on exit.
+void test03()
+{
+ constexpr int kThreads = 4;
+ constexpr int kObjectsPerThread = 5;
+ int dtor_counts[kThreads] = {};
+
+ std::thread threads[kThreads];
+ for (int i = 0; i < kThreads; ++i)
+ threads[i] = std::thread([&dtor_counts, i] {
+ for (int j = 0; j < kObjectsPerThread; ++j)
+ (new Tracked(dtor_counts[i]))->retire();
+ });
+ for (auto& t : threads)
+ t.join();
+
+ for (int i = 0; i < kThreads; ++i)
+ VERIFY( dtor_counts[i] == kObjectsPerThread );
+}
+
+int main()
+{
+ test01();
+ test02();
+ test03();
+}
diff --git a/libstdc++-v3/testsuite/30_threads/hazard_pointer/try_protect.cc
b/libstdc++-v3/testsuite/30_threads/hazard_pointer/try_protect.cc
new file mode 100644
index 00000000000..4af3192a2ad
--- /dev/null
+++ b/libstdc++-v3/testsuite/30_threads/hazard_pointer/try_protect.cc
@@ -0,0 +1,132 @@
+// { dg-do run { target c++26 } }
+// { dg-additional-options "-pthread" { target pthread } }
+// { dg-require-gthreads "" }
+
+// Copyright (C) 2026 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library. This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3. If not see
+// <http://www.gnu.org/licenses/>.
+
+#include <hazard_pointer>
+#include <atomic>
+#include <testsuite_hooks.h>
+
+namespace hpd = std::__hazard_pointer;
+
+namespace
+{
+ struct Node : std::hazard_pointer_obj_base<Node>
+ {
+ int value = 0;
+ };
+
+ struct Tracked : std::hazard_pointer_obj_base<Tracked>
+ {
+ explicit Tracked(int& c) : counter(c) {}
+ ~Tracked() { ++counter; }
+ int& counter;
+ };
+}
+
+// Returns true when pointer is stable.
+void test01()
+{
+ auto hp = std::make_hazard_pointer();
+ Node x;
+ const std::atomic<Node*> src{&x};
+ Node* ptr = src.load(std::memory_order::relaxed);
+ VERIFY( hp.try_protect(ptr, src) );
+}
+
+// Sets ptr on success.
+void test02()
+{
+ auto hp = std::make_hazard_pointer();
+ Node x;
+ x.value = 7;
+ const std::atomic<Node*> src{&x};
+ Node* ptr = src.load(std::memory_order::relaxed);
+ const bool ok = hp.try_protect(ptr, src);
+ VERIFY( ok );
+ VERIFY( ptr == &x );
+}
+
+// Returns true for null source.
+void test03()
+{
+ auto hp = std::make_hazard_pointer();
+ const std::atomic<Node*> src{nullptr};
+ Node* ptr = nullptr;
+ VERIFY( hp.try_protect(ptr, src) );
+ VERIFY( ptr == nullptr );
+}
+
+// Slot still owned after success.
+void test04()
+{
+ auto hp = std::make_hazard_pointer();
+ Node x;
+ const std::atomic<Node*> src{&x};
+ Node* ptr = &x;
+ (void)hp.try_protect(ptr, src);
+ VERIFY( !hp.empty() );
+}
+
+// Protected object not reclaimed after success.
+void test05()
+{
+ auto hp = std::make_hazard_pointer();
+ int dtor_count = 0;
+ auto* obj = new Tracked(dtor_count);
+ const std::atomic<Tracked*> src{obj};
+ Tracked* ptr = src.load(std::memory_order::relaxed);
+ VERIFY( hp.try_protect(ptr, src) );
+
+ obj->retire();
+ hpd::_S_default_domain()._M_synchronize();
+ VERIFY( dtor_count == 0 );
+
+ hp.reset_protection();
+ hpd::_S_default_domain()._M_synchronize();
+ VERIFY( dtor_count == 1 );
+}
+
+// try_protect and protect agree on a stable pointer.
+void test06()
+{
+ Node x;
+ const std::atomic<Node*> src{&x};
+
+ auto hp1 = std::make_hazard_pointer();
+ auto hp2 = std::make_hazard_pointer();
+
+ Node* ptr = src.load(std::memory_order::relaxed);
+ const bool ok = hp1.try_protect(ptr, src);
+ const Node* const via_protect = hp2.protect(src);
+
+ VERIFY( ok );
+ VERIFY( ptr == via_protect );
+ VERIFY( ptr == &x );
+}
+
+int main()
+{
+ test01();
+ test02();
+ test03();
+ test04();
+ test05();
+ test06();
+}
diff --git
a/libstdc++-v3/testsuite/30_threads/hazard_pointer/type_constraints.cc
b/libstdc++-v3/testsuite/30_threads/hazard_pointer/type_constraints.cc
new file mode 100644
index 00000000000..60040bc3c71
--- /dev/null
+++ b/libstdc++-v3/testsuite/30_threads/hazard_pointer/type_constraints.cc
@@ -0,0 +1,56 @@
+// { dg-do compile { target c++26 } }
+// { dg-additional-options "-pthread" { target pthread } }
+// { dg-require-gthreads "" }
+
+// Copyright (C) 2026 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library. This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3. If not see
+// <http://www.gnu.org/licenses/>.
+
+#include <hazard_pointer>
+#include <memory>
+#include <string>
+#include <type_traits>
+
+namespace
+{
+ struct SimpleNode : std::hazard_pointer_obj_base<SimpleNode> {};
+ struct CustomDelNode
+ : std::hazard_pointer_obj_base<CustomDelNode,
+ std::default_delete<CustomDelNode>> {};
+
+ // Indirect derivation must still satisfy _Protectable.
+ struct FurtherDerived : SimpleNode {};
+
+ // Not hazard-protectable.
+ struct PlainClass {};
+}
+
+// _Protectable concept (libstdc++ internal name for HazardProtectable).
+static_assert(std::__hazard_pointer::_Protectable<SimpleNode>);
+static_assert(std::__hazard_pointer::_Protectable<CustomDelNode>);
+static_assert(std::__hazard_pointer::_Protectable<FurtherDerived>);
+
+static_assert(!std::__hazard_pointer::_Protectable<int>);
+static_assert(!std::__hazard_pointer::_Protectable<void>);
+static_assert(!std::__hazard_pointer::_Protectable<int*>);
+
+static_assert(!std::__hazard_pointer::_Protectable<PlainClass>);
+static_assert(!std::__hazard_pointer::_Protectable<std::string>);
+
+// retire() deleter constraints.
+static_assert(std::is_invocable_v<std::default_delete<SimpleNode>,
SimpleNode*>);
+static_assert(std::is_default_constructible_v<std::default_delete<SimpleNode>>);
+static_assert(std::is_move_assignable_v<std::default_delete<SimpleNode>>);
--
2.54.0