diff --git a/CLAUDE.md b/CLAUDE.md index 0e83b6e3..02026d76 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -288,6 +288,40 @@ grep -rn "\`'" doc/modules/ROOT/pages/*.adoc # must return nothing After building, no stray backticks should survive outside code blocks — `grep -n '\`' doc/html/openmethod/.html` should only hit backticks inside C++ comments. +### Reference (MrDocs) constraints + +MrDocs turns the condition of an `enable_if_t` used as a defaulted template argument into a C++20 +requires-clause by copying the **raw source text** spanning the condition expression - it does not +walk the expression tree (cppalliance/mrdocs#1016, which upstream cannot fix until MrDocs can +manipulate expression trees). `BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::)`, which hides the +`detail::` qualification of the exposition-only traits, therefore disappears only when it sits +*before* the first token of the condition: it expands to nothing under `__MRDOCS__`, so it falls +outside the copied range. Anywhere inside the condition - after a `!`, after a `&&`, inside +parentheses - its name is printed verbatim, whatever shape the macro has (function-like as here, +object-like, or a bare `#ifndef __MRDOCS__` around `detail::`). That is why a constraint with two +occurrences renders the first one correctly and leaks the second. + +Two rules keep the reference clean; the long-form version lives next to the macro definition in +`core.hpp`: + +- **The macro must be the first thing in the condition.** Spell a negation + `BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::) Trait == false`, never `!... Trait`. +- **One trait per condition.** When a constraint needs several, give each its own defaulted + template parameter. MrDocs joins them with `&&`, in order, so the rendered clause is unchanged - + and substitution short-circuits at the first failure, so a dependent type in a later condition + (`typename Other::element_type`) is only formed once the earlier ones pass. + +Between them these cover every constraint in the library, so **do not declare a member twice**, an +unqualified copy under `#ifdef __MRDOCS__` beside the real one. Only MrDocs ever compiles that +copy, so the two drift apart silently and the reference then documents a constraint the library +does not have. The `#ifdef __MRDOCS__` blocks that remain remove declarations from the reference +(friends, deleted overloads, the `VirtualTraits` blueprint) rather than restate them. + +Only expressions are affected: types are printed from the AST, so the macro may appear anywhere in +one (`method::operator()` takes +`typename BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::) StripVirtualDecorator::type...` and +renders correctly). After a doc build, `grep -rl MRDOCS doc/html/` must return nothing. + ## Common Development Patterns ### Working with Shared Libraries / DLL Support @@ -379,6 +413,19 @@ For examples: 4. For changes affecting examples: enable `BOOST_OPENMETHOD_BUILD_EXAMPLES` 5. Submit PRs against the `develop` branch +### Posting in public on the maintainer's behalf + +Anything published under the maintainer's account - a GitHub issue or comment, a PR body, a +mailing-list or forum post - must **identify its author in the text itself**, on the first line: + +``` +*(Written by Claude Code, on behalf of @jll63.)* +``` + +The account is a person's, and readers reasonably assume a human wrote what it says; an unlabelled +post misrepresents who is speaking, and a signature in the tool call or the commit trailer is not +visible to them. Ask before posting anyway - the attribution line does not substitute for consent. + ## Important Implementation Details ### Static Registration diff --git a/doc/mrdocs.yml b/doc/mrdocs.yml index 32a92a73..3e4382f5 100644 --- a/doc/mrdocs.yml +++ b/doc/mrdocs.yml @@ -33,6 +33,7 @@ include-macros: - 'BOOST_OPENMETHOD*' exclude-macros: - 'BOOST_OPENMETHOD_DETAIL_*' + - 'BOOST_OPENMETHOD_*UNLESS_MRDOCS' - 'BOOST_OPENMETHOD_GENSYM' - 'BOOST_OPENMETHOD_GUIDE' diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index cedd132a..a7b795a1 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -66,14 +66,39 @@ //! Top namespace of the library. namespace boost::openmethod { +// Hide the `detail::` qualification of the exposition-only traits from MrDocs, +// which documents them as members of `boost::openmethod`. +// +// MrDocs renders the condition of an `enable_if_t` used as a defaulted template +// argument as a C++20 requires-clause, by copying the *raw source text* spanning +// the condition expression - it does not walk the expression tree +// (cppalliance/mrdocs#1016). A macro is thus elided only when it sits *before* +// the first token of the condition, where it expands to nothing under +// `__MRDOCS__` and so falls outside the copied range; anywhere inside the +// condition - after a `!`, after a `&&`, inside parentheses - its name is +// printed verbatim. That holds whatever shape the macro has - the function-like +// form below, an object-like one, or a bare `#ifndef __MRDOCS__` around +// `detail::`. +// +// Hence the rule for a condition mentioning an exposition-only trait: +// +// - `BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::)` must be the first thing in the +// condition. Spell a negation as `Trait == false`, not `!Trait`. +// - One trait per condition. When a constraint needs several, give each its own +// defaulted template parameter - MrDocs joins them with `&&`, in order, so the +// rendered clause is unchanged, and substitution short-circuits at the first +// failure. +// +// Only expressions are affected. Types are printed from the AST, so the macro +// may appear anywhere in one (see `method::operator()`). #ifdef __MRDOCS__ #define BOOST_OPENMETHOD_OPEN_NAMESPACE_DETAIL_UNLESS_MRDOCS #define BOOST_OPENMETHOD_CLOSE_NAMESPACE_DETAIL_UNLESS_MRDOCS -#define BOOST_OPENMETHOD_DETAIL_UNLESS_MRDOCS +#define BOOST_OPENMETHOD_UNLESS_MRDOCS(...) #else #define BOOST_OPENMETHOD_OPEN_NAMESPACE_DETAIL_UNLESS_MRDOCS namespace detail { #define BOOST_OPENMETHOD_CLOSE_NAMESPACE_DETAIL_UNLESS_MRDOCS } -#define BOOST_OPENMETHOD_DETAIL_UNLESS_MRDOCS detail:: +#define BOOST_OPENMETHOD_UNLESS_MRDOCS(...) __VA_ARGS__ #endif namespace detail { @@ -854,7 +879,7 @@ class virtual_ptr { template< class Other, typename = std::enable_if_t< - BOOST_OPENMETHOD_DETAIL_UNLESS_MRDOCS + BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::) IsPolymorphic && std::is_constructible_v>> virtual_ptr(Other& other) @@ -893,7 +918,7 @@ class virtual_ptr { template< class Other, typename = std::enable_if_t< - BOOST_OPENMETHOD_DETAIL_UNLESS_MRDOCS + BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::) IsPolymorphic && std::is_constructible_v>> virtual_ptr(Other* other) @@ -964,7 +989,7 @@ class virtual_ptr { template< class Other, typename = std::enable_if_t< - BOOST_OPENMETHOD_DETAIL_UNLESS_MRDOCS + BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::) IsPolymorphic && std::is_assignable_v>> virtual_ptr& operator=(Other& other) { @@ -1002,7 +1027,7 @@ class virtual_ptr { template< class Other, typename = std::enable_if_t< - BOOST_OPENMETHOD_DETAIL_UNLESS_MRDOCS + BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::) IsPolymorphic && std::is_assignable_v>> virtual_ptr& operator=(Other* other) { @@ -1137,8 +1162,8 @@ class virtual_ptr { template class virtual_ptr< SmartPtr, Registry, - std::enable_if_t< - BOOST_OPENMETHOD_DETAIL_UNLESS_MRDOCS IsSmartPtr>> { + std::enable_if_t>> { #ifndef __MRDOCS__ template @@ -1202,7 +1227,7 @@ class virtual_ptr< detail::box_vptr(detail::null_vptr))), obj(std::move(other.obj)) { } -#ifdef __MRDOCS__ + //! Construct from a (const) smart pointer to a derived class //! //! Set the object pointer with a copy of `other`. Set the v-table pointer @@ -1228,17 +1253,11 @@ class virtual_ptr< template< class Other, typename = std::enable_if_t< - SameSmartPtr && - IsPolymorphic && - std::is_constructible_v>> -#else - template< - class Other, - typename = std::enable_if_t< - detail::SameSmartPtr && - detail::IsPolymorphic && - std::is_constructible_v>> -#endif + BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::) + SameSmartPtr && + std::is_constructible_v>, + typename = std::enable_if_t>> virtual_ptr(const Other& other) : vp(detail::box_vptr( other ? detail::acquire_vptr(*other) @@ -1246,7 +1265,6 @@ class virtual_ptr< obj(other) { } -#if __MRDOCS__ //! Construct from a smart pointer to a derived class //! //! Copy object pointer from `other` to `this`. Set the v-table pointer @@ -1264,17 +1282,11 @@ class virtual_ptr< template< class Other, typename = std::enable_if_t< - SameSmartPtr && - IsPolymorphic && - std::is_constructible_v>> -#else - template< - class Other, - typename = std::enable_if_t< - detail::SameSmartPtr && - detail::IsPolymorphic && - std::is_constructible_v>> -#endif + BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::) + SameSmartPtr && + std::is_constructible_v>, + typename = std::enable_if_t>> virtual_ptr(Other& other) : vp(detail::box_vptr( other ? detail::acquire_vptr(*other) @@ -1282,7 +1294,6 @@ class virtual_ptr< obj(other) { } -#ifdef __MRDOCS__ //! Move-construct from a smart pointer to a derived class //! //! Move object pointer from `other` to `this`. Set the v-table pointer @@ -1307,17 +1318,11 @@ class virtual_ptr< template< class Other, typename = std::enable_if_t< - SameSmartPtr && - IsPolymorphic && - std::is_constructible_v>> -#else - template< - class Other, - typename = std::enable_if_t< - detail::SameSmartPtr && - detail::IsPolymorphic && - std::is_constructible_v>> -#endif + BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::) + SameSmartPtr && + std::is_constructible_v>, + typename = std::enable_if_t>> virtual_ptr(Other&& other) : vp(detail::box_vptr( other ? detail::acquire_vptr(*other) @@ -1343,7 +1348,7 @@ class virtual_ptr< template< class Other, typename = std::enable_if_t< - BOOST_OPENMETHOD_DETAIL_UNLESS_MRDOCS + BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::) SameSmartPtr && std::is_constructible_v>> virtual_ptr(const virtual_ptr& other) @@ -1376,7 +1381,7 @@ class virtual_ptr< template< class Other, typename = std::enable_if_t< - BOOST_OPENMETHOD_DETAIL_UNLESS_MRDOCS + BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::) SameSmartPtr && std::is_constructible_v>> virtual_ptr(virtual_ptr&& other) @@ -1418,11 +1423,11 @@ class virtual_ptr< template< class Other, typename = std::enable_if_t< - BOOST_OPENMETHOD_DETAIL_UNLESS_MRDOCS + BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::) SameSmartPtr && - std::is_assignable_v && - BOOST_OPENMETHOD_DETAIL_UNLESS_MRDOCS - IsPolymorphic>> + std::is_assignable_v>, + typename = std::enable_if_t>> virtual_ptr& operator=(const Other& other) { obj = other; vp = detail::box_vptr( @@ -1454,11 +1459,11 @@ class virtual_ptr< template< class Other, typename = std::enable_if_t< - BOOST_OPENMETHOD_DETAIL_UNLESS_MRDOCS + BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::) SameSmartPtr && - std::is_assignable_v && - BOOST_OPENMETHOD_DETAIL_UNLESS_MRDOCS - IsPolymorphic>> + std::is_assignable_v>, + typename = std::enable_if_t>> virtual_ptr& operator=(Other&& other) { vp = detail::box_vptr( other ? detail::acquire_vptr(*other) : detail::null_vptr); @@ -1484,7 +1489,7 @@ class virtual_ptr< template< class Other, typename = std::enable_if_t< - BOOST_OPENMETHOD_DETAIL_UNLESS_MRDOCS + BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::) SameSmartPtr && std::is_assignable_v>> virtual_ptr& operator=(virtual_ptr& other) { @@ -1513,7 +1518,7 @@ class virtual_ptr< template< class Other, typename = std::enable_if_t< - BOOST_OPENMETHOD_DETAIL_UNLESS_MRDOCS + BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::) SameSmartPtr && std::is_assignable_v>> virtual_ptr& operator=(const virtual_ptr& other) { @@ -1548,7 +1553,7 @@ class virtual_ptr< template< class Other, typename = std::enable_if_t< - BOOST_OPENMETHOD_DETAIL_UNLESS_MRDOCS + BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::) SameSmartPtr && std::is_assignable_v>> virtual_ptr& operator=(virtual_ptr&& other) { @@ -2074,7 +2079,7 @@ class method //! @li @ref ambiguous_call: More than one overrider is applicable, and //! none is more specialized than all the others. //! - auto operator()(typename BOOST_OPENMETHOD_DETAIL_UNLESS_MRDOCS + auto operator()(typename BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::) StripVirtualDecorator::type... args) const -> ReturnType; @@ -2349,7 +2354,7 @@ template< typename Id, typename... Parameters, typename ReturnType, class Registry> BOOST_FORCEINLINE auto method::operator()( - typename BOOST_OPENMETHOD_DETAIL_UNLESS_MRDOCS + typename BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::) StripVirtualDecorator::type... args) const -> ReturnType { using namespace detail; auto pf = resolve(args...); diff --git a/include/boost/openmethod/interop/virtual_any.hpp b/include/boost/openmethod/interop/virtual_any.hpp index 6a7e1b93..11bdf57c 100644 --- a/include/boost/openmethod/interop/virtual_any.hpp +++ b/include/boost/openmethod/interop/virtual_any.hpp @@ -180,8 +180,9 @@ class virtual_any { template< typename T, typename = std::enable_if_t< - !BOOST_OPENMETHOD_DETAIL_UNLESS_MRDOCS - IsVirtualAny> && + BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::) + IsVirtualAny> == false>, + typename = std::enable_if_t< !std::is_same_v, Any> && std::is_constructible_v>> virtual_any(T&& value) @@ -259,8 +260,9 @@ class virtual_any { template< typename T, typename = std::enable_if_t< - !BOOST_OPENMETHOD_DETAIL_UNLESS_MRDOCS - IsVirtualAny> && + BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::) + IsVirtualAny> == false>, + typename = std::enable_if_t< !std::is_same_v, Any> && std::is_constructible_v>> auto operator=(T&& value) -> virtual_any& { @@ -500,7 +502,7 @@ struct virtual_traits&&, Registry> { template class virtual_ptr< Class, Registry, - std::enable_if_t>>> { static_assert( detail::false_t,