diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index 9fd0d476..fe54174e 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -609,7 +609,15 @@ BOOST_OPENMETHOD_OPEN_NAMESPACE_DETAIL_UNLESS_MRDOCS //! //! @tparam Class A class type. //! @tparam Registry A registry. -template +//! @tparam Deferred Ignored. A constraint on a member template of +//! `virtual_ptr` names it here so that the check depends on the member's own +//! template parameter: a default template argument that does not is evaluated +//! when `virtual_ptr` itself is instantiated, and `Class` may still be +//! incomplete then - a `virtual_ptr` member inside `Node`. The same +//! constraints test pointer convertibility first, in a defaulted parameter of +//! its own, so that a candidate that fails it - the copy assignment of that +//! member, which overload resolution tries here too - never gets this far. +template constexpr bool IsPolymorphic = Registry::rtti::template is_polymorphic; //! Test if argument is a smart pointer (exposition only) @@ -971,10 +979,9 @@ class virtual_ptr { //! @li @ref missing_class template< class Other, - typename = std::enable_if_t< - BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::) - IsPolymorphic && - std::is_constructible_v>> + typename = std::enable_if_t>, + typename = std::enable_if_t>> virtual_ptr(Other* other) : vp(detail::box_vptr( detail::acquire_vptr(*other))), @@ -1042,10 +1049,9 @@ class virtual_ptr { //! @li @ref missing_class template< class Other, - typename = std::enable_if_t< - BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::) - IsPolymorphic && - std::is_assignable_v>> + typename = std::enable_if_t>, + typename = std::enable_if_t>> virtual_ptr& operator=(Other& other) { obj = &other; vp = detail::box_vptr( @@ -1080,10 +1086,9 @@ class virtual_ptr { //! @li @ref missing_class template< class Other, - typename = std::enable_if_t< - BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::) - IsPolymorphic && - std::is_assignable_v>> + typename = std::enable_if_t>, + typename = std::enable_if_t>> virtual_ptr& operator=(Other* other) { obj = other; vp = detail::box_vptr( diff --git a/test/test_virtual_ptr_self_referential.cpp b/test/test_virtual_ptr_self_referential.cpp new file mode 100644 index 00000000..a8485581 --- /dev/null +++ b/test/test_virtual_ptr_self_referential.cpp @@ -0,0 +1,59 @@ +// 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) + +// A `virtual_ptr` to the class itself, as a member of that class - a linked +// structure. The class is incomplete where the member is declared, so nothing +// that instantiating `virtual_ptr` entails may need a complete `Node`. +// Three of its member templates did, through a constraint that gcc evaluates +// when the class is instantiated, and libstdc++ 16 makes `is_polymorphic` of +// an incomplete type a hard error where 13 let it through. + +#include +#include + +#define BOOST_TEST_MODULE virtual_ptr_self_referential +#include + +#include + +using namespace boost::openmethod; + +struct Node { + virtual ~Node() = default; + virtual_ptr next; +}; + +struct Leaf : Node {}; + +BOOST_OPENMETHOD_CLASSES(Node, Leaf); + +BOOST_OPENMETHOD(name, (virtual_ptr), std::string); + +BOOST_OPENMETHOD_OVERRIDE(name, (virtual_ptr), std::string) { + return "node"; +} + +BOOST_OPENMETHOD_OVERRIDE(name, (virtual_ptr), std::string) { + return "leaf"; +} + +BOOST_AUTO_TEST_CASE(virtual_ptr_self_referential) { + initialize(); + + Leaf leaf; + Node node; + + // The assignment from `Other&` whose constraint was evaluated too early... + node.next = leaf; + BOOST_TEST(name(node.next) == "leaf"); + + // ...and the implicit copies, for which overload resolution tries the + // same member template with `Other` = `const virtual_ptr`. + Node copy = node; + BOOST_TEST(name(copy.next) == "leaf"); + copy.next = nullptr; + copy = node; + BOOST_TEST(name(copy.next) == "leaf"); +}