diff --git a/include/boost/openmethod/initialize.hpp b/include/boost/openmethod/initialize.hpp index 7f4c4e8c..4d5c96c0 100644 --- a/include/boost/openmethod/initialize.hpp +++ b/include/boost/openmethod/initialize.hpp @@ -355,7 +355,14 @@ struct generic_compiler { } }; - using iterator_category = std::forward_iterator_tag; + // Input, not forward: `reference` is `class_view`, a prvalue made on + // the fly, so two iterators at the same position hand out views at + // different addresses. That fails the forward-iterator requirement + // that `reference` be a real reference - libstdc++ says so outright + // under _GLIBCXX_CONCEPT_CHECKS - and the multipass guarantee with it. + // Everything a policy needs (`++`, `!=`, `*`, `std::distance`) is an + // input-iterator operation. + using iterator_category = std::input_iterator_tag; using value_type = class_view; using difference_type = std::ptrdiff_t; using pointer = arrow_proxy; @@ -890,7 +897,7 @@ void registry::compiler::augment_classes() { indent _(tr); ++tr << type_name(cr.type) << ": " << range{cr.first_base, cr.last_base} - << ", type = " << cr.type << ", &vptr = " << &cr.vptr() + << ", type = " << cr.type << ", &vptr = " << cr.static_vptr << "\n"; } diff --git a/include/boost/openmethod/preamble.hpp b/include/boost/openmethod/preamble.hpp index 0a576d3d..cbf78226 100644 --- a/include/boost/openmethod/preamble.hpp +++ b/include/boost/openmethod/preamble.hpp @@ -319,10 +319,6 @@ struct class_info : static_list::static_link { type_id *first_base, *last_base; bool is_abstract{false}; - auto vptr() const -> const vptr_type& { - return *static_vptr; - } - auto type_id_begin() const { return &type; } @@ -545,13 +541,14 @@ struct InitializeClass { struct InitializeContext { //! Beginning of a range of `InitializeClass` objects. //! - //! @return A forward iterator to the beginning of a range of @ref - //! InitializeClass objects. + //! @return An input iterator to the beginning of a range of @ref + //! InitializeClass objects. It is not a forward iterator: dereferencing it + //! yields a value, not a reference, so the range is single-pass. detail::unspecified classes_begin() const; //! End of a range of `InitializeClass` objects. //! - //! @return A forward iterator to the end of a range of @ref + //! @return An input iterator to the end of a range of @ref //! InitializeClass objects. detail::unspecified classes_end() const; }; diff --git a/test/test_initialize_context.cpp b/test/test_initialize_context.cpp new file mode 100644 index 00000000..f6980860 --- /dev/null +++ b/test/test_initialize_context.cpp @@ -0,0 +1,109 @@ +// 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) + +// The class range a policy's `initialize` receives is an *input* range. Its +// iterator makes the `InitializeClass` view on the fly, so dereferencing +// yields a value, not a reference: two iterators at the same position hand out +// distinct views, and the multipass guarantee a forward iterator owes does not +// hold. It used to advertise `forward_iterator_tag` anyway, which is a promise +// a generic algorithm is entitled to act on. +// +// Pinned here because nothing else in the tree looks at the category: the +// library's own policies only ever `++`, `!=` and dereference. + +#include +#include + +#define BOOST_TEST_MODULE initialize_context +#include + +#include "test_util.hpp" + +#include +#include +#include +#include + +using namespace boost::openmethod; + +// Rides along on a registry that keeps its usual vptr policy, purely to get +// hold of a Context. +struct context_checks { + using category = context_checks; + + template + struct fn { + struct state { + std::size_t classes_seen = 0; + bool values_stable = false; + }; + + template + static void initialize( + const Context& ctx, const std::tuple&) { + using iterator = decltype(ctx.classes_begin()); + using traits = std::iterator_traits; + + static_assert(std::is_same_v< + typename traits::iterator_category, + std::input_iterator_tag>); + + // Why it cannot be a forward iterator: `reference` is the value + // type, so there is nothing for a second pass to refer back to. + static_assert( + std::is_same_v< + typename traits::reference, typename traits::value_type>); + + auto& st = Registry::template state(); + + st.classes_seen = static_cast( + std::distance(ctx.classes_begin(), ctx.classes_end())); + + // Single-pass does not mean unstable: two iterators at the same + // position describe the same class, even though they hand out + // separate views of it. + auto i = ctx.classes_begin(); + auto j = ctx.classes_begin(); + st.values_stable = i != ctx.classes_end() && + i->vptr() == j->vptr() && i->static_vptr() == j->static_vptr(); + } + }; +}; + +template +struct test_reg : test_registry_::template with {}; + +using reg = test_reg<__COUNTER__>; + +struct Animal { + virtual ~Animal() = default; +}; + +struct Dog : Animal {}; +struct Cat : Animal {}; + +struct BOOST_OPENMETHOD_ID(poke); + +using poke = method< + BOOST_OPENMETHOD_ID(poke), auto(virtual_)->std::string, reg>; + +auto poke_animal(Animal&) -> std::string { + return "silence"; +} + +BOOST_AUTO_TEST_CASE(the_class_range_is_an_input_range) { + BOOST_OPENMETHOD_REGISTER(use_classes); + BOOST_OPENMETHOD_REGISTER(poke::override); + + initialize(); + + auto& st = reg::state(); + BOOST_TEST(st.classes_seen == 3u); + BOOST_TEST(st.values_stable); + + // And the registry works, so the policy really did run. + Dog dog; + BOOST_TEST(poke::fn(dog) == "silence"); +}