Fwd: [PATCH 3/3] libstdc++: Add tests for std::hazard_pointer
PaulXiCao
paulxicao7@gmail.com
Sat Jun 20 15:34:16 GMT 2026
I want to give full disclosure on AI usage for transparency.
- implementation of hazard pointer: Used AI as a reviewer and debug helper.
- test code: Heavily relied on AI for initial test code generation.
- libstdc++ infrastructure: Used AI to help understanding code infrastructure
- mailinglist: Used AI to write summaries of my patches for the
mailinglist as I did not have any experience with this channel before.
Looking back they seem to verbose and extensive (compared to other
mails).
On Mon, Jun 8, 2026 at 10:41 PM Paul Xi Cao <paulxicao7@gmail.com> wrote:
>
> 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
>
More information about the Libstdc++
mailing list