Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
109 commits
Select commit Hold shift + click to select a range
f9aacc6
BOOST_OPENMETHOD_DETAIL_HAS_STATIC_FN
jll63 Jan 26, 2026
1eb22d8
inter-operate with 'any'
jll63 Feb 28, 2026
f0aafd3
inter-operate with 'any'
jll63 Mar 7, 2026
4b00b38
Merge branch 'develop' into feature/any
jll63 Jul 28, 2026
3a1fbc1
Merge branch 'develop' into feature/any
jll63 Jul 30, 2026
4914053
support std::any by mutable and xvalue reference
jll63 Jul 31, 2026
810a981
support boost::any
jll63 Jul 31, 2026
f086985
type_vptr -> vptr
jll63 Aug 2, 2026
7ecd96c
dynamic_vptr -> vptr
jll63 Aug 8, 2026
65cd1b5
fix leftovers of the dynamic_vptr -> vptr rename
jll63 Aug 8, 2026
57b6c32
add virtual_any
jll63 Aug 8, 2026
bfe8537
Merge branch 'feature/any' into feature/type_erasure
jll63 Aug 8, 2026
475b05b
probe virtual_traits::vptr with the argument type, not type_id
jll63 Aug 8, 2026
a117a28
any traits: pass the any through to catch-all overriders
jll63 Aug 8, 2026
a6d56d2
use_*_any_types: do not register a trailing registry as a class
jll63 Aug 8, 2026
495abc2
support boost::type_erasure (#21)
jll63 Aug 8, 2026
78143c8
fix infinite recursion in virtual_any's vptr friend on MSVC
jll63 Aug 8, 2026
01c863d
Merge branch 'feature/mrdocs-macros' into feature/any
jll63 Aug 8, 2026
9dcba28
doc: an Interoperation page, and reference examples for the `any`s
jll63 Aug 8, 2026
8bcb1a3
alias use_std_any_types and use_boost_any_types
jll63 Aug 8, 2026
50d5ec9
support catch-all overriders on plain `any` virtual parameters
jll63 Aug 8, 2026
ff11651
doc: lead the Interoperation page with `virtual_`, not `virtual_any`
jll63 Aug 8, 2026
acdd8e0
test: make the virtual_any-by-value compile-fail test actually fail o…
jll63 Aug 8, 2026
e946dd2
doc: fix the broken `any` header links on the Headers page
jll63 Aug 8, 2026
f1d3cd8
doc: narrow the Interoperation page to `any`
jll63 Aug 8, 2026
0474536
Merge branch 'feature/any' into feature/type_erasure
jll63 Aug 8, 2026
9663974
doc: an Interoperation with Boost.TypeErasure page, and reference exa…
jll63 Aug 8, 2026
15ddee5
doc: fix the broken type_erasure header link on the Headers page
jll63 Aug 8, 2026
3bedb21
doc: document `vptr` in VirtualTraits and VptrFn
jll63 Aug 9, 2026
608f1b6
Merge branch 'feature/mrdocs-macros' into feature/any
jll63 Aug 9, 2026
a735c45
doc: make the `any` header links follow the deployment too
jll63 Aug 9, 2026
65904e3
doc: inline the final_virtual_ptr example instead of linking to it
jll63 Aug 9, 2026
1fc8ea3
doc: hide the deleted `final_virtual_ptr` overloads from the reference
jll63 Aug 9, 2026
597f03b
add the openmethod_vptr concept (#21)
jll63 Aug 9, 2026
6c6cb44
doc: hide the deleted final_virtual_ptr overloads from the reference
jll63 Aug 9, 2026
8737e86
doc: tighten the boost_type_erasure entry on the Headers page
jll63 Aug 9, 2026
437c666
reject virtual_ptr over classes with a boost_openmethod_vptr overload
jll63 Aug 9, 2026
4704d43
Merge branch 'feature/any' into feature/type_erasure
jll63 Aug 9, 2026
e3a2537
doc: openmethod_vptr anys are not wrapped in virtual_any
jll63 Aug 9, 2026
b1d78b8
doc: make the type_erasure header link follow the deployment
jll63 Aug 9, 2026
dbdd951
doc: fix the plural any's rendering with literal backslashes and back…
jll63 Aug 9, 2026
516606c
doc: trim the any-interop entries on the Headers page
jll63 Aug 9, 2026
3e98203
Merge branch 'feature/any' into feature/type_erasure
jll63 Aug 9, 2026
c42b1bd
doc: trim the type_erasure entry on the Headers page to match
jll63 Aug 9, 2026
9da7fd9
doc: give the example Dog a ctor so make_any_virtual forwards its arg…
jll63 Aug 9, 2026
b7706b3
add a make_any_virtual overload that deduces the type of the value
jll63 Aug 9, 2026
4b0c30a
Revert the value-deducing make_any_virtual overload and the example ctor
jll63 Aug 9, 2026
69b5d43
remove the make_*_virtual factories
jll63 Aug 9, 2026
df3c0b7
add virtual_any_ref, a non-owning counterpart of virtual_any
jll63 Aug 9, 2026
1109d69
Merge branch 'feature/any' into feature/type_erasure
jll63 Aug 9, 2026
c9807ab
doc: stop advertising virtual_any for type_erasure anys
jll63 Aug 9, 2026
cb44af6
test: exercise virtual_any_ref with boost::any
jll63 Aug 10, 2026
e0797c1
Merge branch 'feature/any' into feature/type_erasure
jll63 Aug 10, 2026
8b21152
Merge branch 'feature/mrdocs-macros' into feature/any
jll63 Aug 10, 2026
9221d15
doc: point the `any` header links at GitHub too
jll63 Aug 10, 2026
0ac37a3
Merge branch 'feature/any' into feature/type_erasure
jll63 Aug 10, 2026
32e064c
doc: point the type_erasure header link at GitHub too
jll63 Aug 10, 2026
a131c7f
fix MSVC: constrain the type_erasure boost_openmethod_vptr friend
jll63 Aug 10, 2026
47c80a8
test: make the type_erasure by-value guard fire on every compiler
jll63 Aug 10, 2026
338108e
Merge branch 'feature/any' into feature/type_erasure
jll63 Aug 10, 2026
b45feb4
Merge branch 'feature/mrdocs-macros' into feature/any
jll63 Aug 10, 2026
f3440db
ci: split the macOS 10.15 Drone stages by C++ standard
jll63 Aug 12, 2026
1788457
doc, test: cover virtual_any with type_erasure::any
jll63 Aug 15, 2026
d579766
doc: give the acknowledgment its own section on both interop pages
jll63 Aug 15, 2026
408fe46
doc: credit the interop design in the headers, matching the pages
jll63 Aug 15, 2026
235344f
doc: touch up `any` doc
jll63 Aug 15, 2026
926574c
Merge branch 'feature/mrdocs-macros' into feature/any
jll63 Aug 15, 2026
8183c83
Merge branch 'feature/any' into feature/type_erasure
jll63 Aug 15, 2026
8f2384b
doc: touch up TypeErasure doc
jll63 Aug 15, 2026
1f8ed77
doc: add the virtual_traits::vptr step to the dispatch lists
jll63 Aug 15, 2026
5c033b5
Merge branch 'feature/any' into feature/type_erasure
jll63 Aug 15, 2026
161fdf8
doc: fix an article
jll63 Aug 15, 2026
eb92a95
Merge branch 'develop' into feature/any
jll63 Aug 16, 2026
a01efed
doc: more `any` touch-up
jll63 Aug 16, 2026
57116d5
Merge branch 'feature/any' into feature/type_erasure
jll63 Aug 16, 2026
156b343
doc: drop "borrow" from the type_erasure interop prose
jll63 Aug 16, 2026
49bfbf5
require std_rtti in the any interop
jll63 Aug 16, 2026
f2d7f23
Merge branch 'feature/any' into feature/type_erasure
jll63 Aug 16, 2026
8027dd9
require std_rtti in the type_erasure interop
jll63 Aug 16, 2026
6730af4
doc: state the any interop's rtti requirement as what, not how
jll63 Aug 16, 2026
eaf01f5
Merge branch 'feature/any' into feature/type_erasure
jll63 Aug 16, 2026
f298c6f
doc: state the type_erasure rtti requirement as what, not how
jll63 Aug 16, 2026
20a2a96
doc, test: openmethod_vptr lifts the std_rtti requirement
jll63 Aug 16, 2026
7018caf
any: document IsVirtualAny, and test that it rejects a handle
jll63 Aug 17, 2026
378736b
Merge branch 'feature/any' into feature/type_erasure
jll63 Aug 17, 2026
658acee
ci: empty commit to re-trigger the docs preview build
jll63 Aug 18, 2026
1948667
doc: clarify virtual_any_ref comparison to Boost.TypeErasure any
jll63 Aug 19, 2026
c9ca434
any: rename virtual_any_ref to virtual_any_ptr, in its own header
jll63 Aug 20, 2026
55a1a36
Merge branch 'feature/any' into feature/type_erasure
jll63 Aug 20, 2026
23e2704
doc: list virtual_any_ptr among the type_erasure interop's forms
jll63 Aug 20, 2026
d7c76db
Merge branch 'develop' into feature/type_erasure
jll63 Aug 20, 2026
2039495
CI: depend on Boost.TypeErasure's headers, not its compiled library
jll63 Aug 21, 2026
29dda3b
CMake: consume Boost.TypeErasure's headers without linking it
jll63 Aug 21, 2026
cc8002d
CI: pin the Boost.TypeErasure dependency to its shared flavor
jll63 Aug 21, 2026
9e57158
CI: stop naming the Boost.TypeErasure target at all; fix the CMake re…
jll63 Aug 21, 2026
d355ad9
any: remove virtual_any_ptr
jll63 Aug 22, 2026
238ea08
Merge branch 'feature/any' into feature/type_erasure
jll63 Aug 22, 2026
5e83dfe
any: register contained types automatically
jll63 Aug 22, 2026
e31b367
Merge branch 'feature/any' into feature/type_erasure
jll63 Aug 22, 2026
c80fc57
type_erasure: register bound types automatically
jll63 Aug 22, 2026
7ecae1a
any: dispatch virtual_any through virtual_traits::vptr
jll63 Aug 22, 2026
6c20fd4
type_erasure: test openmethod_vptr with indirect vptrs
jll63 Aug 22, 2026
a5d2b77
type_erasure: say what openmethod_vptr actually costs
jll63 Aug 22, 2026
b2de7ff
CLAUDE.md: Intel-syntax disassembly, and the adoc table mechanism
jll63 Aug 22, 2026
aca0fa4
CLAUDE.md: condense the shared-libraries section
jll63 Aug 22, 2026
6bbbdf1
Merge branch 'feature/any' into feature/type_erasure
jll63 Aug 22, 2026
8e14446
doc: size the any parameter table to its content
jll63 Aug 22, 2026
ee2f349
Merge branch 'feature/any' into feature/type_erasure
jll63 Aug 22, 2026
0b285a8
doc: fix the type_erasure page's tables and concept prose
jll63 Aug 22, 2026
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
229 changes: 110 additions & 119 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,12 @@ The project uses clang-format with an LLVM-based style:
- `AllowShortFunctionsOnASingleLine: false`
- No short blocks, if statements, or loops on single lines

### Disassembly
Always show disassembly in **Intel syntax**, never AT&T (the toolchain's default
here). Add the flag to the invocation before pasting any output:
- `objdump -dC -M intel --no-show-raw-insn`
- `gcc -S -masm=intel` / `clang -S -masm=intel`

### Compiler Requirements
Tests require these C++17 features (checked by Boost.Build):
- auto nontype template params
Expand All @@ -159,131 +165,116 @@ Tests require these C++17 features (checked by Boost.Build):
- structured bindings
- `<charconv>`, `<string_view>`, `<variant>` headers

### Documentation (AsciiDoc)

Prose lives in `doc/modules/ROOT/pages/*.adoc`; explanations belong there, not in comments inside
the example sources under `doc/modules/ROOT/examples/`, which are pulled into the rendered page
verbatim through `include::example$file.cpp[tag=content]`. Pages hard-wrap at ~79 columns and use
`cpp:name[]` for API names that have a reference page.

**Render the docs; do not just eyeball the `.adoc`.** `doc/build_antora.sh` (~2 min, writes the
gitignored `doc/html/`) is the only way to catch markup that is silently mis-parsed — asciidoctor
emits no warning for it.

**Side-by-side comparisons use a table with AsciiDoc cells.** The house shape is
`[cols="1,1"]` + `|===` with a header row (`interop_any.adoc`, `registries_and_policies.adoc`,
`shared_libraries.adoc`). A cell holding a *block* - a code listing, a nested list - must be
introduced with `a|`, not `|`. The `a` makes the cell content parsed as AsciiDoc; without it the
`[source,...]` / `----` markup renders literally, and asciidoctor says nothing.
`interop_type_erasure.adoc` compares two dispatch sequences that way:

```
[cols="1,1"]
|===
| `openmethod_vptr` | `virtual_any`

a|
[source,asm]
----
mov rax, qword ptr [rdi + 32]
----

a|
[source,asm]
----
jmp qword ptr [rax + 8*rcx]
----

|===
```

`|` starts a new cell, so cell content containing one must escape it as `\|`. Quick structural
check before rendering - both counts must be even, and every `[source,X]` must be followed by
`----`:

```bash
grep -c '^----$' doc/modules/ROOT/pages/<page>.adoc
grep -c '^|===$' doc/modules/ROOT/pages/<page>.adoc
```

**The backtick-apostrophe trap**: never write a possessive right after a code span. Asciidoctor
parses ``` `any`'s ``` as `` ` `` + `any` + the **`` `' `` curly-apostrophe shorthand**, which
consumes the *closing* backtick; the opening one is then left unmatched and pairs with the next
backtick in the same paragraph. Two things break at once — a literal `` ` `` appears in the output,
and the following code span loses its `<code>` formatting:

```
source: is part of the `any`'s type - whereas the `typeid_of`-based dispatch above
rendered: is part of the any's type - whereas the `typeid_of-based dispatch above
```

Reword instead: "the reference types of the `any`", "separate from that of `default_registry`".
``{apos}`` also works and matches the house style (`shared_libraries.adoc` uses ``{empty}`` for
plurals: ``` `virtual_ptr`{empty}s ```), but rewording is safer and reads better. Before building:

```bash
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/<page>.html` should only hit backticks inside C++ comments.

## Common Development Patterns

### Working with Shared Libraries / DLL Support

**Overview**: The library supports shared library usage across modules by sharing the registry's
state through an export/import decoration of a single symbol. On Windows (and Cygwin) the decoration
is dllexport/dllimport; on ELF it is `visibility("default")` on the export side. In the common case
off Windows the decoration can be omitted entirely — the state then has ordinary external linkage
and is shared by the dynamic linker — but that only works if the program is *not* built with hidden
visibility. Under `-fvisibility=hidden` (e.g. the Boost super-project's `BoostRoot.cmake`) an
implicitly instantiated `st` is a COMDAT that gets internalized to a per-module local symbol, so
the export/import macros must be used on ELF too (they emit a single strong, default-visibility
explicit instantiation that the other modules import).

**One shared state variable**: All of a registry's mutable state — the class/method/overrider
lists *and* every stateful policy's `state` (held together in the `registry_state_type::policies`
tuple) — lives in a single variable, `registry_state<Registry>::st` of type
`detail::registry_state_type<Registry>`. A registry reaches it through `Registry::state()`. Sharing
a registry across a DLL boundary therefore means sharing this one symbol.

`registry_state` (in `boost::openmethod`) is a deliberately thin, function-free class whose only
member is the static `st`. It is kept *separate* from `registry_state_type` (the struct holding the
actual fields, in `detail`) because MSVC only honors `dllexport`/`dllimport` on a *whole-class*
explicit instantiation — not on a variable template (clients silently get a private copy) nor on a
static-data-member instantiation (error C2720) — and dllexporting `registry_state_type` directly
would also decorate its member functions and the policies' nested `state` types, which MSVC rejects
(error C2513). A one-member, function-free class is the only shape MSVC will export as a whole and
import via `extern template`.

**Mechanism — `extern template` / explicit instantiation**: the shared symbol is
`registry_state<Registry::registry_type>::st`, where `registry_type` is the `registry<Policy...>`
*base* of the registry struct (that is what `registry::state()` uses — never key on the derived
struct). The owning module compiles, in exactly one TU, an exported explicit instantiation
definition; clients compile an imported explicit instantiation declaration, so they reference
the owner's symbol instead of instantiating their own copy:
```cpp
// owner (one TU):
template struct BOOST_SYMBOL_EXPORT registry_state<R::registry_type>;
// clients:
extern template struct BOOST_SYMBOL_IMPORT registry_state<R::registry_type>;
```
`BOOST_SYMBOL_EXPORT`/`BOOST_SYMBOL_IMPORT` are dllexport/dllimport on Windows and
`visibility("default")` / empty on ELF, so the same two lines serve both platforms. This is no
longer guarded by `_WIN32`: on ELF the pair is what makes the state shareable under hidden
visibility.

**Registries are structs, not aliases — do not "simplify" this**: `default_registry` (and the
documented custom-registry pattern) is deliberately a *struct deriving from* `registry<Policy...>`,
never a type alias. The short struct name keeps mangled/linker names short for everything keyed on
the registry (methods, virtual_ptrs, `static_vptr`, registrars...); an alias would expand to the
full policy list in all of those symbols. This is also why the `::registry_type` spelling in the
explicit instantiations above cannot be avoided: an explicit instantiation instantiates exactly the
specialization written, so making `registry_state<default_registry>` work would require
`default_registry` to *be* its base (an alias) — rejected for the mangled-name reason. Only the
shared state symbol carries the full policy list, which is accepted.

**Usage**: three macros, each taking the registry as an argument, so the same three serve
`default_registry`, `indirect_registry` and user-defined registries. Everything they emit is fully
qualified, so callers never open `namespace boost::openmethod`:
A registry's entire mutable state - the class/method/overrider lists plus every stateful policy's
`state` - lives in one variable, `registry_state<Registry>::st`. Sharing a registry across modules
means sharing that one symbol. Three macros do it, each taking the registry as an argument and
emitting fully qualified names, so callers never open `namespace boost::openmethod`:

```cpp
// header, every TU of a client module
BOOST_OPENMETHOD_IMPORT_REGISTRY(boost::openmethod::default_registry);
// header, every TU of the owning module
BOOST_OPENMETHOD_EXPORT_REGISTRY(boost::openmethod::default_registry);
// exactly one .cpp of the owning module
BOOST_OPENMETHOD_INSTANTIATE_REGISTRY(boost::openmethod::default_registry);
BOOST_OPENMETHOD_IMPORT_REGISTRY(R); // header, every TU of a client module
BOOST_OPENMETHOD_EXPORT_REGISTRY(R); // header, every TU of the owning module
BOOST_OPENMETHOD_INSTANTIATE_REGISTRY(R); // exactly one .cpp of the owning module
```
The owning module uses two: `EXPORT` in the shared header, `INSTANTIATE` once. Note
`::registry_type` inside the expansions: the state is keyed on the `registry<...>` base, never the
derived struct.

**Why three macros and not raw incantations** — the underlying explicit instantiations are not
portable, and each spelling fails on one platform while compiling silently on the other. The
macros branch on `BOOST_HAS_DECLSPEC`:
- *declspec platforms* (Windows/Cygwin/MinGW): `__declspec(dllexport)` and `extern` are
incompatible on an explicit instantiation — MSVC emits `warning C4910` and, with warnings-as-
errors, fails. It is also unnecessary there, visibility not being a PE concept. So `EXPORT`
expands to nothing (`static_assert(true)`, to swallow the `;`) and `INSTANTIATE` carries the
`dllexport`.
- *ELF and Mach-O*: the attribute must be on the **declaration**; repeating it on the definition is
`error: type attributes ignored after type is already defined [-Werror=attributes]` on GCC, which
clang accepts silently. So `EXPORT` carries it and `INSTANTIATE` carries none.

**`EXPORT` is load-bearing on ELF**, not documentation: a TU of the owning module with neither
`EXPORT` nor `INSTANTIATE` instantiates the state implicitly, and under `-fvisibility=hidden` that
copy is module-local. ELF merges COMDATs at the *most restrictive* visibility, so the merged symbol
becomes local, the module exports nothing, and clients fail to link with an undefined reference to
`registry_state<...>::st`. `test/implicit_shared_libraries/custom_registry/lib2.cpp` is a second
owner TU kept solely to guard that path. Placement within the instantiating TU does not matter
(verified with `readelf` under `-fvisibility=hidden`).

**Methods need no decoration**: method objects are *consolidated* across modules at `initialize()`
time, not shared via a single symbol, so `BOOST_OPENMETHOD(...)` takes no declspec argument.

See `doc/modules/ROOT/examples/shared_libs/` — one self-contained example per subdirectory
(`implicit_linking/`, `dynamic_loading/`, `indirect_vptr/`), each with its own `animals.hpp`,
`main.cpp` and `extensions.cpp` so every file spells out its export/import macro unconditionally —
plus `test/dynamic_loading/` and `test/implicit_shared_libraries/` for the tests.

**Dynamic Loading Test** (`test/dynamic_loading/`): verifies that the registry state is a single
shared symbol across modules. `registry_state_id()` (in `registry.hpp`) returns the registry-state
address (`test_registry::id()`); `main.cpp`'s `same_ids()` compares two such addresses (registry vs.
method, registry vs. overrider) and asserts they are identical. (Policy state lives inside
`registry_state_type`, so the registry-state address is the one shared symbol.) Files:
- `registry.hpp` — defines `test_registry` (indirect iff `BOOST_OPENMETHOD_DEFAULT_REGISTRY` is defined on the command line), then emits `BOOST_OPENMETHOD_{EXPORT,IMPORT}_REGISTRY(test_registry)` according to whether the module compiles with `EXPORT_REGISTRY`; defines `registry_state_id()`
- `classes.hpp` — `Animal`/`Dog`/`Cat` definitions (marked `BOOST_SYMBOL_VISIBLE` so their RTTI stays
default-visibility under the hidden-visibility CMake variant below) + `make_dog`/`make_cat`
- `method.hpp` — declares the `speak`/`meet` methods (no declspec arguments)
- `shared_overrider.hpp` — one `speak` overrider for `Cat`, included identically by `method.cpp` and
`overrider.cpp` to exercise cross-module overrider deduplication (the same overrider registered by
two modules must not be treated as ambiguous)
- `registry.cpp` — compiled with `EXPORT_REGISTRY`; the shared library that owns and exports the registry state
- `method.cpp` — client (imports the registry state); defines base overriders (including the shared
Cat one), exports C entry points
- `overrider.cpp` — dynamically loaded at runtime; adds a Dog overrider and the shared Cat overrider
- `main.cpp` — links the registry lib, dlopens the method and overrider libs, checks `same_ids`, calls
`initialize()`, tests cross-module dispatch (including the Cat overrider-dedup and class-dedup
regression checks)

CMake builds five variants: `_default`/`_indirect` (dll-owned state) and `_exereg_default`/
`_exereg_indirect` (exe-owned state), crossed with the default/indirect registry, plus `_hidden_vis`
(forces `CXX_VISIBILITY_PRESET hidden` on every target to reproduce, on a standalone build, the
configuration where `augment_classes()`'s class-dedup must key on `(type, static_vptr)` rather than
`type` alone). b2's Jamfile only builds the dll-owned default/indirect pair; it does not currently
have a hidden-visibility variant.

Methods need no decoration: method objects are *consolidated* across modules at `initialize()`
time, not shared through a symbol.

**Do not hand-write the underlying explicit instantiations.** They are not portable, and each
spelling compiles silently on one platform while failing on the other. On declspec platforms
`__declspec(dllexport)` and `extern` are incompatible on an explicit instantiation (MSVC warning
C4910), so `EXPORT` expands to nothing and `INSTANTIATE` carries the attribute; on ELF and Mach-O
the attribute must be on the *declaration*, and repeating it on the definition is an error on GCC,
so `EXPORT` carries it and `INSTANTIATE` carries none. The macros branch on `BOOST_HAS_DECLSPEC`.

**`EXPORT` is load-bearing on ELF, not documentation.** Under `-fvisibility=hidden` (e.g. the Boost
super-project's `BoostRoot.cmake`) an owner TU with neither `EXPORT` nor `INSTANTIATE` instantiates
the state implicitly as a COMDAT; ELF merges COMDATs at the *most restrictive* visibility, so the
merged symbol goes module-local, the module exports nothing, and clients fail to link.
`test/implicit_shared_libraries/custom_registry/lib2.cpp` exists solely to guard that path.

**Registries are structs deriving from `registry<Policy...>`, never aliases - do not "simplify"
this.** The short struct name keeps mangled names short for everything keyed on the registry
(methods, virtual_ptrs, `static_vptr`, registrars); an alias would expand the full policy list into
all of them. That is also why the state is keyed on `Registry::registry_type` - the `registry<...>`
base - and never on the derived struct. `registry_state` is likewise a deliberately thin,
function-free class: that is the only shape MSVC will export whole and import via `extern template`.

One self-contained example per subdirectory of `doc/modules/ROOT/examples/shared_libs/`; tests in
`test/dynamic_loading/` (whose `registry_state_id()` is compared across modules to prove the state
is a single symbol) and `test/implicit_shared_libraries/`.

### Custom RTTI
When `<typeinfo>` is unavailable or insufficient, use static_rtti or implement custom RTTI. See `doc/modules/ROOT/examples/custom_rtti/` and policies in `include/boost/openmethod/policies/`.
Expand Down
31 changes: 30 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,17 @@ set(

if (BOOST_OPENMETHOD_BUILD_TESTS OR BOOST_OPENMETHOD_MRDOCS_BUILD)
list(APPEND BOOST_OPENMETHOD_DEPENDENCIES Boost::smart_ptr)
list(APPEND BOOST_OPENMETHOD_DEPENDENCIES Boost::any)
list(APPEND BOOST_OPENMETHOD_DEPENDENCIES Boost::type_erasure)
# ... but for its headers only. Boost.TypeErasure's compiled part is one
# file, dynamic_binding.cpp, which nothing here references and which links
# Boost::thread - so linking Boost::type_erasure builds Boost.Thread and,
# through it, Boost.Container, Boost.Chrono, Boost.date_time and
# Boost.Atomic. Two of those do not build under -Werror on the Windows CI
# toolchains, which took the whole test suite down with them. Consume the
# usage requirements without linking, as test/Jamfile does with <use>.
# ($<COMPILE_ONLY:> expresses this directly, but needs CMake 3.27.)
list(APPEND BOOST_OPENMETHOD_COMPILE_ONLY_DEPENDENCIES Boost::type_erasure)
endif()

foreach (BOOST_OPENMETHOD_DEPENDENCY ${BOOST_OPENMETHOD_DEPENDENCIES})
Expand Down Expand Up @@ -176,7 +187,25 @@ set_property(GLOBAL PROPERTY USE_FOLDERS ON)
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR}/include/boost PREFIX "" FILES ${BOOST_OPENMETHOD_HEADERS})

function(boost_openmethod_setup_properties target)
target_link_libraries(${target} INTERFACE ${BOOST_OPENMETHOD_DEPENDENCIES})
set(link_dependencies ${BOOST_OPENMETHOD_DEPENDENCIES})

# $<COMPILE_ONLY:> is exactly "the usage requirements, without the link".
# Being a generator expression, it does not care whether the dependency's
# target has been defined yet - which the super-project, driving this from
# BOOST_INCLUDE_LIBRARIES, does not guarantee. Anything that inspects the
# target at configure time does care, and breaks when the super-project
# happens to configure openmethod first.
#
# It needs CMake 3.27, well past the floor declared here. Below that, link
# normally: slower, and it builds Boost.Thread and friends, but correct.
if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.27)
foreach (dependency ${BOOST_OPENMETHOD_COMPILE_ONLY_DEPENDENCIES})
list(REMOVE_ITEM link_dependencies ${dependency})
list(APPEND link_dependencies $<COMPILE_ONLY:${dependency}>)
endforeach()
endif()

target_link_libraries(${target} INTERFACE ${link_dependencies})
endfunction()

add_library(boost_openmethod INTERFACE)
Expand Down
Loading
Loading