diff --git a/doc/modules/ROOT/pages/ref_headers.adoc b/doc/modules/ROOT/pages/ref_headers.adoc index 3003bb6b..0cde474d 100644 --- a/doc/modules/ROOT/pages/ref_headers.adoc +++ b/doc/modules/ROOT/pages/ref_headers.adoc @@ -29,6 +29,9 @@ parameters: * xref:#std_unique_ptr[``] to use `std::unique_ptr` in virtual parameters. +* xref:#std_weak_ptr[``] to track +objects with `std::weak_ptr` without losing their v-table pointer. + ## High-level Headers [#core] @@ -72,6 +75,14 @@ Provides a `virtual_traits` specialization that makes it possible to use a Provides a `virtual_traits` specialization that makes it possible to use a `std::unique_ptr` in place of a raw pointer or reference in virtual parameters. +[#std_weak_ptr] +### link:{headers-url}/boost/openmethod/interop/std_weak_ptr.hpp[] + +Provides cpp:weak_virtual_ptr[], a `virtual_ptr` that tracks an object with a +`std::weak_ptr` and remembers its v-table pointer. It cannot be used in virtual +parameters; its `lock` function returns a cpp:shared_virtual_ptr[], without a +hash table lookup. + [#boost_intrusive_ptr] ### link:{headers-url}/boost/openmethod/interop/boost_intrusive_ptr.hpp[] diff --git a/doc/modules/ROOT/pages/smart_pointers.adoc b/doc/modules/ROOT/pages/smart_pointers.adoc index bd19e994..3231ae16 100644 --- a/doc/modules/ROOT/pages/smart_pointers.adoc +++ b/doc/modules/ROOT/pages/smart_pointers.adoc @@ -67,6 +67,47 @@ in ``, and support for ``. `` does not include smart pointer headers, so they must be included explicitly. +[#weak_pointers] +## Weak Pointers + +A `std::weak_ptr` observes an object without keeping it alive. Since the object +may be gone, there is nothing to dispatch on: a weak pointer cannot be used in a +virtual parameter, and neither can a `virtual_ptr` to a weak pointer. Still, an +object that is tracked by weak pointers - in a cache, an observer list, or a +back pointer - is typically an object that methods will be called on, once a +weak pointer has been locked. + +- cpp:weak_virtual_ptr[] is an alias for `virtual_ptr>` + +A `weak_virtual_ptr` is a storage facility. It is constructed from a +`shared_virtual_ptr` (or from a `std::shared_ptr` or a `std::weak_ptr`), and +it remembers the v-table pointer along with the weak pointer. It cannot be +dereferenced. Its `lock` function returns a `shared_virtual_ptr`, which can be +passed to methods. Since the v-table pointer is copied, not looked up, `lock` +costs no more than `std::weak_ptr::lock`. It returns an empty +`shared_virtual_ptr` if the object no longer exists. + +[source,c++] +---- +shared_virtual_ptr animal = make_shared_virtual(); +weak_virtual_ptr observer = animal; + +std::cout << poke(observer.lock()) << "\n"; // bark + +animal = nullptr; +std::cout << std::boolalpha << observer.expired() << "\n"; // true +---- + +Remembering the v-table pointer is safe because a `std::weak_ptr` keeps the +control block alive: once the object has been destroyed, the weak pointer stays +expired, and the v-table pointer can never be applied to another object. + +A `weak_virtual_ptr` converts to a `weak_virtual_ptr` to a base class, but not +to a plain or a shared `virtual_ptr`. A cast to a derived class requires the +object: use `lock`, then `cast`. Support for `std::weak_ptr` is provided in +``, which also includes the +`std::shared_ptr` header. + Here is a variation of the AST example that uses dynamic allocation and unique pointers: diff --git a/doc/modules/ROOT/snippets/smart_pointers.cpp b/doc/modules/ROOT/snippets/smart_pointers.cpp index 264a8e41..c8b11f70 100644 --- a/doc/modules/ROOT/snippets/smart_pointers.cpp +++ b/doc/modules/ROOT/snippets/smart_pointers.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #define BOOST_TEST_MODULE openmethod #include @@ -194,3 +195,35 @@ BOOST_AUTO_TEST_CASE(unique_ptr_examples) { BOOST_TEST(cout.str() == "bark\nhiss\n"); } } + +BOOST_AUTO_TEST_CASE(weak_ptr_examples) { + initialize(); + + { + using namespace shared_vptr; + capture_cout cout; + + // tag::weak_lock[] + shared_virtual_ptr animal = make_shared_virtual(); + weak_virtual_ptr observer = animal; + + std::cout << poke(observer.lock()) << "\n"; // bark + + animal = nullptr; + std::cout << std::boolalpha << observer.expired() << "\n"; // true + // end::weak_lock[] + + BOOST_TEST(cout.str() == "bark\ntrue\n"); + } + + { + // tag::weak_virtual_ptr_alias[] + shared_virtual_ptr animal = make_shared_virtual(); + weak_virtual_ptr observer = animal; + std::weak_ptr weak = observer.pointer(); + + BOOST_TEST(animal.pointer().use_count() == 1); + BOOST_TEST(weak.lock() == animal.pointer()); + // end::weak_virtual_ptr_alias[] + } +} diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index cad8a165..dbbd399f 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -594,6 +594,13 @@ struct same_smart_ptr_aux< typename virtual_traits::template rebind< typename Other::element_type>> {}; +template +constexpr bool has_get = false; + +template +constexpr bool + has_get().get())>> = true; + } // namespace detail BOOST_OPENMETHOD_OPEN_NAMESPACE_DETAIL_UNLESS_MRDOCS @@ -1007,8 +1014,10 @@ class virtual_ptr { //! @li @c Other's object pointer must be assignable to a @c Class*. template< class Other, - typename = std::enable_if_t::element_type*>>> + typename = std::enable_if_t< + std::is_constructible_v< + Class*, typename virtual_ptr::element_type*> && + detail::has_get>>> virtual_ptr(const virtual_ptr& other) : vp(other.vp), obj(other.get()) { } @@ -1117,8 +1126,11 @@ class virtual_ptr { //! @li @c Other's object pointer must be assignable to a @c Class*. template< class Other, - typename = std::enable_if_t::element_type*>>> + typename = std::enable_if_t< + std::is_assignable_v< + Class*&, + typename virtual_ptr::element_type*> && + detail::has_get>>> virtual_ptr& operator=(const virtual_ptr& other) { obj = other.get(); vp = other.vp; diff --git a/include/boost/openmethod/interop/std_weak_ptr.hpp b/include/boost/openmethod/interop/std_weak_ptr.hpp new file mode 100644 index 00000000..a554aed5 --- /dev/null +++ b/include/boost/openmethod/interop/std_weak_ptr.hpp @@ -0,0 +1,455 @@ +// Copyright (c) 2017-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_OPENMETHOD_INTEROP_WEAK_PTR_HPP +#define BOOST_OPENMETHOD_INTEROP_WEAK_PTR_HPP + +#include +#include + +namespace boost::openmethod { +namespace detail { + +// A weak pointer may refer to an object that no longer exists, so there is +// nothing to dispatch on. `virtual_traits` is deliberately *not* specialized +// for `std::weak_ptr`: that keeps `IsSmartPtr` false, which is what lets the +// hand-written `virtual_ptr` specialization below win over the generic smart +// pointer one. These specializations only replace the vague diagnostics that +// would result from using a weak pointer as a virtual parameter with a useful +// one. + +template +struct validate_method_parameter>, Registry, void> : + std::false_type { + static_assert( + false_t, + "a weak pointer cannot be a virtual parameter; call lock() first"); +}; + +template +struct validate_method_parameter< + virtual_ptr, Registry>, Registry, void> : std::false_type { + static_assert( + false_t, + "a weak pointer cannot be a virtual parameter; call lock() first"); +}; + +template +struct validate_method_parameter< + virtual_ptr, Registry>&, Registry, void> : + std::false_type { + static_assert( + false_t, + "a weak pointer cannot be a virtual parameter; call lock() first"); +}; + +template +struct validate_method_parameter< + const virtual_ptr, Registry>&, Registry, void> : + std::false_type { + static_assert( + false_t, + "a weak pointer cannot be a virtual parameter; call lock() first"); +}; + +} // namespace detail + +//! Wide pointer combining a `std::weak_ptr` to an object and a pointer to its +//! v-table +//! +//! This specialization of `virtual_ptr` tracks the object with a +//! `std::weak_ptr`, and remembers its v-table pointer. It is a storage +//! facility: unlike other kinds of `virtual_ptr`, it cannot be dereferenced, +//! and it cannot be used as a virtual parameter, because the object may no +//! longer exist. Instead, call `lock()` to obtain a @ref shared_virtual_ptr, +//! then use it as usual. Since the v-table pointer is copied from the weak +//! pointer, `lock()` costs no more than `std::weak_ptr::lock()`: no hash table +//! lookup is needed. +//! +//! Remembering the v-table pointer is safe: a `std::weak_ptr` keeps the +//! control block alive, so once the object is destroyed, the weak pointer +//! stays expired. The v-table pointer can never be applied to another object. +//! +//! @par Example +//! include:smart_pointers.cpp#classes;weak_lock +//! +//! @tparam Class The class of the object, possibly cv-qualified +//! @tparam Registry The registry in which `Class` is registered +//! +//! @see @ref weak_virtual_ptr +//! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) +template +class virtual_ptr, Registry, void> { +#ifndef __MRDOCS__ + template + friend class virtual_ptr; +#endif + + static constexpr bool use_indirect_vptrs = Registry::has_indirect_vptr; + + std::conditional_t vp; + std::weak_ptr obj; + + template + static auto vptr_of(const std::shared_ptr& other) { + return detail::box_vptr( + other ? detail::acquire_vptr(*other) : detail::null_vptr); + } + + public: + //! Class pointed to by the `std::weak_ptr` + using element_type = Class; + + //! Default constructor + //! + //! Construct an empty `std::weak_ptr`. Set the v-table pointer to + //! `nullptr`. + virtual_ptr() : + vp(detail::box_vptr(detail::null_vptr)) { + } + + //! Construct from `nullptr` + //! + //! Construct an empty `std::weak_ptr`. Set the v-table pointer to + //! `nullptr`. + //! + //! @param value A `nullptr`. + explicit virtual_ptr(std::nullptr_t) : + vp(detail::box_vptr(detail::null_vptr)) { + } + + virtual_ptr(const virtual_ptr& other) = default; + + virtual_ptr(virtual_ptr&& other) : + vp(std::exchange( + other.vp, detail::box_vptr(detail::null_vptr))), + obj(std::move(other.obj)) { + } + + //! Construct from a `shared_virtual_ptr` to a derived class + //! + //! Copy the v-table pointer from `other`. Construct the `std::weak_ptr` + //! from the `std::shared_ptr` held by `other`. + //! + //! `Other` is _not_ required to be a polymorphic class: the v-table + //! pointer is already known. + //! + //! @par Example + //! include:smart_pointers.cpp#classes;weak_lock + //! + //! @param other A `shared_virtual_ptr` to an object of a class derived from + //! `Class`. + //! + //! @par Requirements + //! @li `std::weak_ptr` must be constructible from + //! `const std::shared_ptr&`. + template< + class Other, + typename = std::enable_if_t, const std::shared_ptr&>>> + virtual_ptr(const virtual_ptr, Registry>& other) : + vp(other.vp), obj(other.obj) { + } + + //! Construct from a `weak_virtual_ptr` to a derived class + //! + //! Copy the v-table pointer and the `std::weak_ptr` from `other`. + //! + //! @param other A `weak_virtual_ptr` to an object of a class derived from + //! `Class`. + //! + //! @par Requirements + //! @li `std::weak_ptr` must be constructible from + //! `const std::weak_ptr&`. + template< + class Other, + typename = std::enable_if_t, const std::weak_ptr&>>> + virtual_ptr(const virtual_ptr, Registry>& other) : + vp(other.vp), obj(other.obj) { + } + + //! Move-construct from a `weak_virtual_ptr` to a derived class + //! + //! Copy the v-table pointer from `other`, and set it to `nullptr` in + //! `other`. Move the `std::weak_ptr` from `other`. + //! + //! @param other A `weak_virtual_ptr` to an object of a class derived from + //! `Class`. + //! + //! @par Requirements + //! @li `std::weak_ptr` must be constructible from + //! `std::weak_ptr&&`. + template< + class Other, + typename = std::enable_if_t, std::weak_ptr&&>>> + virtual_ptr(virtual_ptr, Registry>&& other) : + vp(std::exchange( + other.vp, detail::box_vptr(detail::null_vptr))), + obj(std::move(other.obj)) { + } + + //! Construct from a `std::shared_ptr` to a derived class + //! + //! Construct the `std::weak_ptr` from `other`. Set the v-table pointer + //! according to the dynamic type of `*other`. + //! + //! @param other A `std::shared_ptr` to a polymorphic object. + //! + //! @par Requirements + //! @li `Other` must be a polymorphic class, according to the `rtti` + //! policy of `Registry`. + //! @li `std::weak_ptr` must be constructible from + //! `const std::shared_ptr&`. + template< + class Other, + typename = std::enable_if_t< + BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::) + IsPolymorphic && + std::is_constructible_v< + std::weak_ptr, const std::shared_ptr&>>> + virtual_ptr(const std::shared_ptr& other) : + vp(vptr_of(other)), obj(other) { + } + + //! Construct from a `std::weak_ptr` to a derived class + //! + //! Construct the `std::weak_ptr` from `other`. Lock `other` to find the + //! dynamic type of the object, and set the v-table pointer accordingly. If + //! `other` has expired, the v-table pointer is set to `nullptr`. + //! + //! @param other A `std::weak_ptr` to a polymorphic object. + //! + //! @par Requirements + //! @li `Other` must be a polymorphic class, according to the `rtti` + //! policy of `Registry`. + //! @li `std::weak_ptr` must be constructible from + //! `const std::weak_ptr&`. + template< + class Other, + typename = std::enable_if_t< + BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::) + IsPolymorphic && + std::is_constructible_v< + std::weak_ptr, const std::weak_ptr&>>> + virtual_ptr(const std::weak_ptr& other) : + vp(vptr_of(other.lock())), obj(other) { + } + + //! Assign from `nullptr` + //! + //! Reset the `std::weak_ptr`. Set the v-table pointer to `nullptr`. + //! + //! @param value A `nullptr`. + virtual_ptr& operator=(std::nullptr_t) { + obj.reset(); + vp = detail::box_vptr(detail::null_vptr); + return *this; + } + + virtual_ptr& operator=(const virtual_ptr& other) = default; + + virtual_ptr& operator=(virtual_ptr&& other) { + vp = std::exchange( + other.vp, detail::box_vptr(detail::null_vptr)); + obj = std::move(other.obj); + return *this; + } + + //! Assign from a `shared_virtual_ptr` to a derived class + //! + //! Copy the v-table pointer from `other`. Assign the `std::weak_ptr` from + //! the `std::shared_ptr` held by `other`. + //! + //! `Other` is _not_ required to be a polymorphic class: the v-table + //! pointer is already known. + //! + //! @param other A `shared_virtual_ptr` to an object of a class derived from + //! `Class`. + //! + //! @par Requirements + //! @li `std::weak_ptr` must be assignable from + //! `const std::shared_ptr&`. + template< + class Other, + typename = std::enable_if_t&, const std::shared_ptr&>>> + virtual_ptr& operator=( + const virtual_ptr, Registry>& other) { + vp = other.vp; + obj = other.obj; + return *this; + } + + //! Assign from a `weak_virtual_ptr` to a derived class + //! + //! Copy the v-table pointer and the `std::weak_ptr` from `other`. + //! + //! @param other A `weak_virtual_ptr` to an object of a class derived from + //! `Class`. + //! + //! @par Requirements + //! @li `std::weak_ptr` must be assignable from + //! `const std::weak_ptr&`. + template< + class Other, + typename = std::enable_if_t&, const std::weak_ptr&>>> + virtual_ptr& operator=( + const virtual_ptr, Registry>& other) { + vp = other.vp; + obj = other.obj; + return *this; + } + + //! Move-assign from a `weak_virtual_ptr` to a derived class + //! + //! Copy the v-table pointer from `other`, and set it to `nullptr` in + //! `other`. Move the `std::weak_ptr` from `other`. + //! + //! @param other A `weak_virtual_ptr` to an object of a class derived from + //! `Class`. + //! + //! @par Requirements + //! @li `std::weak_ptr` must be assignable from + //! `std::weak_ptr&&`. + template< + class Other, + typename = std::enable_if_t&, std::weak_ptr&&>>> + virtual_ptr& operator=( + virtual_ptr, Registry>&& other) { + vp = std::exchange( + other.vp, detail::box_vptr(detail::null_vptr)); + obj = std::move(other.obj); + return *this; + } + + //! Assign from a `std::shared_ptr` to a derived class + //! + //! Assign the `std::weak_ptr` from `other`. Set the v-table pointer + //! according to the dynamic type of `*other`. + //! + //! @param other A `std::shared_ptr` to a polymorphic object. + //! + //! @par Requirements + //! @li `Other` must be a polymorphic class, according to the `rtti` + //! policy of `Registry`. + //! @li `std::weak_ptr` must be assignable from + //! `const std::shared_ptr&`. + template< + class Other, + typename = std::enable_if_t< + BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::) + IsPolymorphic && + std::is_assignable_v< + std::weak_ptr&, const std::shared_ptr&>>> + virtual_ptr& operator=(const std::shared_ptr& other) { + vp = vptr_of(other); + obj = other; + return *this; + } + + //! Assign from a `std::weak_ptr` to a derived class + //! + //! Assign the `std::weak_ptr` from `other`. Lock `other` to find the + //! dynamic type of the object, and set the v-table pointer accordingly. If + //! `other` has expired, the v-table pointer is set to `nullptr`. + //! + //! @param other A `std::weak_ptr` to a polymorphic object. + //! + //! @par Requirements + //! @li `Other` must be a polymorphic class, according to the `rtti` + //! policy of `Registry`. + //! @li `std::weak_ptr` must be assignable from + //! `const std::weak_ptr&`. + template< + class Other, + typename = std::enable_if_t< + BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::) + IsPolymorphic && + std::is_assignable_v< + std::weak_ptr&, const std::weak_ptr&>>> + virtual_ptr& operator=(const std::weak_ptr& other) { + vp = vptr_of(other.lock()); + obj = other; + return *this; + } + + //! Lock the weak pointer + //! + //! Return a `shared_virtual_ptr` to the object, using the remembered + //! v-table pointer. No hash table lookup is performed. + //! + //! @par Example + //! include:smart_pointers.cpp#classes;weak_lock + //! + //! @return A `shared_virtual_ptr` to the object if it still exists, or an + //! empty `shared_virtual_ptr` with a `nullptr` v-table pointer otherwise. + auto lock() const -> virtual_ptr, Registry> { + if (auto locked = obj.lock()) { + return virtual_ptr, Registry>( + std::move(locked), vp); + } + + return virtual_ptr, Registry>(); + } + + //! Check whether the object still exists + //! + //! @return `true` if the `std::weak_ptr` is empty or the object has been + //! destroyed, `false` otherwise. + auto expired() const noexcept -> bool { + return obj.expired(); + } + + //! Get the number of `std::shared_ptr` objects sharing the object + //! + //! @return The result of `std::weak_ptr::use_count`. + auto use_count() const noexcept -> long { + return obj.use_count(); + } + + //! Release the reference to the object + //! + //! Reset the `std::weak_ptr`. Set the v-table pointer to `nullptr`. + void reset() noexcept { + obj.reset(); + vp = detail::box_vptr(detail::null_vptr); + } + + //! Get the weak pointer to the object + //! + //! @return A const reference to the `std::weak_ptr` + auto pointer() const noexcept -> const std::weak_ptr& { + return obj; + } + + //! Get the v-table pointer + //! + //! @return A pointer to the v-table remembered when the `virtual_ptr` was + //! created or assigned, or `nullptr`. + auto vptr() const { + return detail::unbox_vptr(this->vp); + } +}; + +//! Alias for a `virtual_ptr>`. +//! +//! @par Example +//! include:smart_pointers.cpp#weak_virtual_ptr_alias +//! +//! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) +template +using weak_virtual_ptr = virtual_ptr, Registry>; + +namespace aliases { +using boost::openmethod::weak_virtual_ptr; +} // namespace aliases + +} // namespace boost::openmethod + +#endif diff --git a/test/compile_fail_weak_ptr_parameter.cpp b/test/compile_fail_weak_ptr_parameter.cpp new file mode 100644 index 00000000..fa645b23 --- /dev/null +++ b/test/compile_fail_weak_ptr_parameter.cpp @@ -0,0 +1,29 @@ +// Copyright (c) 2017-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +// Expected diagnostic, as a CMake regex (see CMakeLists.txt). +// expected-error: a weak pointer cannot be a virtual parameter; call lock\(\) first + +#include +#include + +using namespace boost::openmethod; + +struct Animal { + virtual ~Animal() { + } +}; +struct Cat : Animal {}; + +BOOST_OPENMETHOD(poke, (virtual_>), void); + +BOOST_OPENMETHOD_OVERRIDE(poke, (std::weak_ptr), void) { +} + +int main() { + auto felix = std::make_shared(); + poke(std::weak_ptr(felix)); + return 0; +} diff --git a/test/compile_fail_weak_virtual_ptr_parameter.cpp b/test/compile_fail_weak_virtual_ptr_parameter.cpp new file mode 100644 index 00000000..1a3666c5 --- /dev/null +++ b/test/compile_fail_weak_virtual_ptr_parameter.cpp @@ -0,0 +1,29 @@ +// Copyright (c) 2017-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +// Expected diagnostic, as a CMake regex (see CMakeLists.txt). +// expected-error: a weak pointer cannot be a virtual parameter; call lock\(\) first + +#include +#include + +using namespace boost::openmethod; + +struct Animal { + virtual ~Animal() { + } +}; +struct Cat : Animal {}; + +BOOST_OPENMETHOD(poke, (weak_virtual_ptr), void); + +BOOST_OPENMETHOD_OVERRIDE(poke, (weak_virtual_ptr), void) { +} + +int main() { + auto felix = std::make_shared(); + poke(weak_virtual_ptr(felix)); + return 0; +} diff --git a/test/test_weak_virtual_ptr.cpp b/test/test_weak_virtual_ptr.cpp new file mode 100644 index 00000000..9feb1873 --- /dev/null +++ b/test/test_weak_virtual_ptr.cpp @@ -0,0 +1,315 @@ +// Copyright (c) 2017-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include + +#define BOOST_TEST_MODULE weak_virtual_ptr +#include + +#include "test_virtual_ptr_value_semantics.hpp" + +#include +#include +#include + +// A weak virtual_ptr is neither a smart virtual_ptr in the `IsSmartPtr` sense +// (no `virtual_traits`, no `rebind`) nor a plain one. +static_assert(!IsSmartPtr, default_registry>); + +static_assert(std::is_same_v::element_type, Animal>); +static_assert(std::is_same_v< + decltype(std::declval>().lock()), + shared_virtual_ptr>); +static_assert(std::is_same_v< + decltype(std::declval>().pointer()), + const std::weak_ptr&>); + +// Construction is allowed from shared and weak pointers, virtual or not... +static_assert(std::is_constructible_v< + weak_virtual_ptr, shared_virtual_ptr>); +static_assert( + std::is_constructible_v, shared_virtual_ptr>); +static_assert( + std::is_constructible_v, weak_virtual_ptr>); +static_assert( + std::is_constructible_v, std::shared_ptr>); +static_assert( + std::is_constructible_v, std::weak_ptr>); +static_assert(std::is_constructible_v< + weak_virtual_ptr, shared_virtual_ptr>); + +// ...but not from a plain pointer, reference or virtual_ptr, nor from a +// different class or a const object... +static_assert(!std::is_constructible_v, Animal&>); +static_assert(!std::is_constructible_v, Animal*>); +static_assert( + !std::is_constructible_v, virtual_ptr>); +static_assert( + !std::is_constructible_v, shared_virtual_ptr>); +static_assert(!std::is_constructible_v< + weak_virtual_ptr, shared_virtual_ptr>); +static_assert( + !std::is_constructible_v< + weak_virtual_ptr, std::shared_ptr>); +static_assert(!std::is_constructible_v< + weak_virtual_ptr, std::weak_ptr>); + +// ...and a weak virtual_ptr converts to nothing but another weak virtual_ptr. +static_assert( + !std::is_constructible_v, weak_virtual_ptr>); +static_assert( + !std::is_assignable_v&, weak_virtual_ptr>); +static_assert(!std::is_constructible_v< + shared_virtual_ptr, weak_virtual_ptr>); +static_assert(!std::is_assignable_v< + shared_virtual_ptr&, weak_virtual_ptr>); +static_assert(!std::is_constructible_v< + std::shared_ptr, weak_virtual_ptr>); + +BOOST_AUTO_TEST_CASE_TEMPLATE( + weak_virtual_ptr_from_shared_virtual_ptr, Registry, test_policies) { + init_test(); + + auto snoopy = std::make_shared(); + shared_virtual_ptr shared(snoopy); + + weak_virtual_ptr weak(shared); + BOOST_TEST(!weak.expired()); + BOOST_TEST(weak.use_count() == 2); + BOOST_TEST(weak.vptr() == Registry::template static_vptr); + BOOST_TEST(weak.pointer().lock() == snoopy); + + { + auto locked = weak.lock(); + static_assert(std::is_same_v< + decltype(locked), shared_virtual_ptr>); + BOOST_TEST(locked.get() == snoopy.get()); + BOOST_TEST(locked.vptr() == Registry::template static_vptr); + BOOST_TEST(weak.use_count() == 3); + } + + BOOST_TEST(weak.use_count() == 2); + + shared = nullptr; + snoopy.reset(); + BOOST_TEST(weak.expired()); + BOOST_TEST(weak.use_count() == 0); + + auto locked = weak.lock(); + BOOST_TEST(locked.get() == nullptr); + BOOST_TEST(locked.vptr() == nullptr); + + weak.reset(); + BOOST_TEST(weak.expired()); + BOOST_TEST(weak.vptr() == nullptr); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE( + weak_virtual_ptr_from_std_pointers, Registry, test_policies) { + init_test(); + + auto felix = std::make_shared(); + + { + weak_virtual_ptr weak(felix); + BOOST_TEST(weak.vptr() == Registry::template static_vptr); + BOOST_TEST(weak.lock().get() == felix.get()); + } + + { + std::weak_ptr std_weak = felix; + weak_virtual_ptr weak(std_weak); + BOOST_TEST(weak.vptr() == Registry::template static_vptr); + BOOST_TEST(weak.lock().get() == felix.get()); + } + + { + // an expired std::weak_ptr yields an expired weak virtual_ptr + std::weak_ptr std_weak; + { + auto dead = std::make_shared(); + std_weak = dead; + } + + weak_virtual_ptr weak(std_weak); + BOOST_TEST(weak.expired()); + BOOST_TEST(weak.vptr() == nullptr); + BOOST_TEST(weak.lock().get() == nullptr); + } + + { + // an empty std::shared_ptr yields an empty weak virtual_ptr + weak_virtual_ptr weak{std::shared_ptr()}; + BOOST_TEST(weak.expired()); + BOOST_TEST(weak.vptr() == nullptr); + } +} + +BOOST_AUTO_TEST_CASE_TEMPLATE( + weak_virtual_ptr_default_and_nullptr, Registry, test_policies) { + init_test(); + + { + weak_virtual_ptr weak; + BOOST_TEST(weak.expired()); + BOOST_TEST(weak.use_count() == 0); + BOOST_TEST(weak.vptr() == nullptr); + BOOST_TEST(weak.lock().get() == nullptr); + } + + { + weak_virtual_ptr weak(nullptr); + BOOST_TEST(weak.expired()); + BOOST_TEST(weak.vptr() == nullptr); + } + + { + auto snoopy = std::make_shared(); + weak_virtual_ptr weak(snoopy); + BOOST_TEST(!weak.expired()); + + weak = nullptr; + BOOST_TEST(weak.expired()); + BOOST_TEST(weak.vptr() == nullptr); + } +} + +BOOST_AUTO_TEST_CASE_TEMPLATE( + weak_virtual_ptr_assign, Registry, test_policies) { + init_test(); + + auto snoopy = std::make_shared(); + auto felix = std::make_shared(); + weak_virtual_ptr weak; + + weak = shared_virtual_ptr(snoopy); + BOOST_TEST(weak.lock().get() == snoopy.get()); + BOOST_TEST(weak.vptr() == Registry::template static_vptr); + + weak = shared_virtual_ptr(felix); + BOOST_TEST(weak.lock().get() == felix.get()); + BOOST_TEST(weak.vptr() == Registry::template static_vptr); + + weak = snoopy; + BOOST_TEST(weak.lock().get() == snoopy.get()); + BOOST_TEST(weak.vptr() == Registry::template static_vptr); + + weak = std::weak_ptr(felix); + BOOST_TEST(weak.lock().get() == felix.get()); + BOOST_TEST(weak.vptr() == Registry::template static_vptr); + + weak_virtual_ptr weak_dog(snoopy); + weak = weak_dog; + BOOST_TEST(weak.lock().get() == snoopy.get()); + BOOST_TEST(weak.vptr() == Registry::template static_vptr); + BOOST_TEST(!weak_dog.expired()); + + weak = *&weak; // self-assignment + BOOST_TEST(weak.lock().get() == snoopy.get()); + BOOST_TEST(weak.vptr() == Registry::template static_vptr); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE( + weak_virtual_ptr_copy_move, Registry, test_policies) { + init_test(); + + auto snoopy = std::make_shared(); + weak_virtual_ptr weak_dog(snoopy); + + { + weak_virtual_ptr copy(weak_dog); + BOOST_TEST(copy.lock().get() == snoopy.get()); + BOOST_TEST(copy.vptr() == Registry::template static_vptr); + BOOST_TEST(weak_dog.lock().get() == snoopy.get()); + } + + { + // upcast, copying + weak_virtual_ptr base(weak_dog); + BOOST_TEST(base.lock().get() == snoopy.get()); + BOOST_TEST(base.vptr() == Registry::template static_vptr); + BOOST_TEST(weak_dog.lock().get() == snoopy.get()); + } + + { + weak_virtual_ptr source(snoopy); + weak_virtual_ptr moved(std::move(source)); + BOOST_TEST(moved.lock().get() == snoopy.get()); + BOOST_TEST(moved.vptr() == Registry::template static_vptr); + BOOST_TEST(source.expired()); + BOOST_TEST(source.vptr() == nullptr); + } + + { + // upcast, moving + weak_virtual_ptr source(snoopy); + weak_virtual_ptr moved(std::move(source)); + BOOST_TEST(moved.lock().get() == snoopy.get()); + BOOST_TEST(moved.vptr() == Registry::template static_vptr); + BOOST_TEST(source.expired()); + BOOST_TEST(source.vptr() == nullptr); + } + + { + weak_virtual_ptr source(snoopy); + weak_virtual_ptr moved; + moved = std::move(source); + BOOST_TEST(moved.lock().get() == snoopy.get()); + BOOST_TEST(moved.vptr() == Registry::template static_vptr); + BOOST_TEST(source.expired()); + BOOST_TEST(source.vptr() == nullptr); + } +} + +BOOST_AUTO_TEST_CASE_TEMPLATE( + weak_virtual_ptr_non_polymorphic, Registry, test_policies) { + // The v-table pointer is copied from the shared_virtual_ptr, so the class + // need not be polymorphic; only the vptr lookup would require that. + BOOST_OPENMETHOD_REGISTER(use_classes); + init_test(); + + auto shared = make_shared_virtual(); + weak_virtual_ptr weak(shared); + BOOST_TEST(weak.vptr() == Registry::template static_vptr); + BOOST_TEST(weak.lock().get() == shared.get()); + BOOST_TEST( + weak.lock().vptr() == Registry::template static_vptr); +} + +struct BOOST_OPENMETHOD_ID(poke); + +// Namespace-scope templates: gcc before 13 does not accept the static member +// functions of a local class as template arguments (no linkage). +template +auto poke_dog(shared_virtual_ptr) -> std::string { + return "bark"; +} + +template +auto poke_cat(shared_virtual_ptr) -> std::string { + return "hiss"; +} + +BOOST_AUTO_TEST_CASE_TEMPLATE( + weak_virtual_ptr_dispatch, Registry, test_policies) { + using poke = method< + BOOST_OPENMETHOD_ID(poke), + auto(shared_virtual_ptr)->std::string, Registry>; + + BOOST_OPENMETHOD_REGISTER( + typename poke::template override< + poke_dog, poke_cat>); + + init_test(); + + auto snoopy = std::make_shared(); + auto felix = std::make_shared(); + weak_virtual_ptr weak_dog(snoopy); + weak_virtual_ptr weak_cat(felix); + + // lock, then dispatch + BOOST_TEST(poke::fn(weak_dog.lock()) == "bark"); + BOOST_TEST(poke::fn(weak_cat.lock()) == "hiss"); +}