From d44aa593cd09b3d8399eea69d196790a3ea55bad Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 12 Sep 2026 02:42:24 -0400 Subject: [PATCH] virtual_ptr: allow a virtual_ptr to the class as a member of the class struct Node { virtual ~Node() = default; virtual_ptr next; }; failed to compile with gcc 16: `invalid use of incomplete type 'struct Node'`, from `std::is_polymorphic_v`. Three member templates of `virtual_ptr` - the constructor from `Other*`, and the assignments from `Other&` and `Other*` - constrained themselves with `IsPolymorphic && std::is_constructible_v` in a default template argument. The first operand does not depend on `Other`, so gcc evaluates it when the class itself is instantiated - for the member declaration, where `Node` is still incomplete. libstdc++ 13 answered `is_polymorphic` on an incomplete type; 16 rejects it, as the standard allows. The check now names `Other`, through an ignored trailing pack on the exposition-only `IsPolymorphic`, which makes it dependent and defers it to the first use of the member - where the class is complete. And it sits in a defaulted parameter of its own, after the pointer-convertibility test: clang reaches these candidates during overload resolution for the member's implicit copy assignment, with `Other` = `const virtual_ptr`, and substitution stops at the first condition that fails. That is the shape CLAUDE.md prescribes for MrDocs anyway, and the rendered constraint reads the same. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01MDgWtJsC4KihJHq73cuCcV --- include/boost/openmethod/core.hpp | 31 +++++++----- test/test_virtual_ptr_self_referential.cpp | 59 ++++++++++++++++++++++ 2 files changed, 77 insertions(+), 13 deletions(-) create mode 100644 test/test_virtual_ptr_self_referential.cpp 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"); +}