Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 18 additions & 13 deletions include/boost/openmethod/core.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -609,7 +609,15 @@ BOOST_OPENMETHOD_OPEN_NAMESPACE_DETAIL_UNLESS_MRDOCS
//!
//! @tparam Class A class type.
//! @tparam Registry A registry.
template<class Class, class Registry>
//! @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<Class>` itself is instantiated, and `Class` may still be
//! incomplete then - a `virtual_ptr<Node>` 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<class Class, class Registry, class... Deferred>
constexpr bool IsPolymorphic = Registry::rtti::template is_polymorphic<Class>;

//! Test if argument is a smart pointer (exposition only)
Expand Down Expand Up @@ -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<Class, Registry> &&
std::is_constructible_v<Class*, Other*>>>
typename = std::enable_if_t<std::is_constructible_v<Class*, Other*>>,
typename = std::enable_if_t<BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::)
IsPolymorphic<Class, Registry, Other>>>
virtual_ptr(Other* other) :
vp(detail::box_vptr<use_indirect_vptrs>(
detail::acquire_vptr<Registry>(*other))),
Expand Down Expand Up @@ -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<Class, Registry> &&
std::is_assignable_v<Class*&, Other*>>>
typename = std::enable_if_t<std::is_assignable_v<Class*&, Other*>>,
typename = std::enable_if_t<BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::)
IsPolymorphic<Class, Registry, Other>>>
virtual_ptr& operator=(Other& other) {
obj = &other;
vp = detail::box_vptr<use_indirect_vptrs>(
Expand Down Expand Up @@ -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<Class, Registry> &&
std::is_assignable_v<Class*&, Other*>>>
typename = std::enable_if_t<std::is_assignable_v<Class*&, Other*>>,
typename = std::enable_if_t<BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::)
IsPolymorphic<Class, Registry, Other>>>
virtual_ptr& operator=(Other* other) {
obj = other;
vp = detail::box_vptr<use_indirect_vptrs>(
Expand Down
59 changes: 59 additions & 0 deletions test/test_virtual_ptr_self_referential.cpp
Original file line number Diff line number Diff line change
@@ -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<Node>` 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 <boost/openmethod.hpp>
#include <boost/openmethod/initialize.hpp>

#define BOOST_TEST_MODULE virtual_ptr_self_referential
#include <boost/test/unit_test.hpp>

#include <string>

using namespace boost::openmethod;

struct Node {
virtual ~Node() = default;
virtual_ptr<Node> next;
};

struct Leaf : Node {};

BOOST_OPENMETHOD_CLASSES(Node, Leaf);

BOOST_OPENMETHOD(name, (virtual_ptr<Node>), std::string);

BOOST_OPENMETHOD_OVERRIDE(name, (virtual_ptr<Node>), std::string) {
return "node";
}

BOOST_OPENMETHOD_OVERRIDE(name, (virtual_ptr<Leaf>), 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>`.
Node copy = node;
BOOST_TEST(name(copy.next) == "leaf");
copy.next = nullptr;
copy = node;
BOOST_TEST(name(copy.next) == "leaf");
}
Loading