commit 5e0fa8ee4bf9e8c01677f2e37b4951ded901d70f Author: Jean-Louis Leroy Date: Mon Oct 16 12:32:07 2017 -0400 inception diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..b26254b --- /dev/null +++ b/.clang-format @@ -0,0 +1,136 @@ +--- +Language: Cpp +# BasedOnStyle: LLVM +AccessModifierOffset: -2 +AlignAfterOpenBracket: AlwaysBreak +AlignConsecutiveMacros: false +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Right +AlignOperands: false +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: false +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: true +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DeriveLineEnding: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + SortPriority: 0 + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + SortPriority: 0 + - Regex: '.*' + Priority: 1 + SortPriority: 0 +IncludeIsMainRegex: '(Test)?$' +IncludeIsMainSourceRegex: '' +IndentCaseLabels: false +IndentGotoLabels: true +#IndentPPDirectives: true +IndentWidth: 4 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Left +ReflowComments: false +SortIncludes: false +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInConditionalStatement: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceBeforeSquareBrackets: false +Standard: Latest +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 8 +UseCRLF: false +UseTab: Never +... diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..776a237 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,33 @@ +--- +Checks: 'clang-diagnostic-*,clang-analyzer-*,modernize-use-trailing-return-type' +WarningsAsErrors: '' +HeaderFileExtensions: + - '' + - h + - hh + - hpp + - hxx +ImplementationFileExtensions: + - c + - cc + - cpp + - cxx +ExcludeHeaderFilterRegex: '' +FormatStyle: file +User: jll +CheckOptions: + cert-dcl16-c.NewSuffixes: 'L;LL;LU;LLU' + cert-err33-c.AllowCastToVoid: 'true' + cert-err33-c.CheckedFunctions: '^::aligned_alloc;^::asctime_s;^::at_quick_exit;^::atexit;^::bsearch;^::bsearch_s;^::btowc;^::c16rtomb;^::c32rtomb;^::calloc;^::clock;^::cnd_broadcast;^::cnd_init;^::cnd_signal;^::cnd_timedwait;^::cnd_wait;^::ctime_s;^::fclose;^::fflush;^::fgetc;^::fgetpos;^::fgets;^::fgetwc;^::fopen;^::fopen_s;^::fprintf;^::fprintf_s;^::fputc;^::fputs;^::fputwc;^::fputws;^::fread;^::freopen;^::freopen_s;^::fscanf;^::fscanf_s;^::fseek;^::fsetpos;^::ftell;^::fwprintf;^::fwprintf_s;^::fwrite;^::fwscanf;^::fwscanf_s;^::getc;^::getchar;^::getenv;^::getenv_s;^::gets_s;^::getwc;^::getwchar;^::gmtime;^::gmtime_s;^::localtime;^::localtime_s;^::malloc;^::mbrtoc16;^::mbrtoc32;^::mbsrtowcs;^::mbsrtowcs_s;^::mbstowcs;^::mbstowcs_s;^::memchr;^::mktime;^::mtx_init;^::mtx_lock;^::mtx_timedlock;^::mtx_trylock;^::mtx_unlock;^::printf_s;^::putc;^::putwc;^::raise;^::realloc;^::remove;^::rename;^::scanf;^::scanf_s;^::setlocale;^::setvbuf;^::signal;^::snprintf;^::snprintf_s;^::sprintf;^::sprintf_s;^::sscanf;^::sscanf_s;^::strchr;^::strerror_s;^::strftime;^::strpbrk;^::strrchr;^::strstr;^::strtod;^::strtof;^::strtoimax;^::strtok;^::strtok_s;^::strtol;^::strtold;^::strtoll;^::strtoul;^::strtoull;^::strtoumax;^::strxfrm;^::swprintf;^::swprintf_s;^::swscanf;^::swscanf_s;^::thrd_create;^::thrd_detach;^::thrd_join;^::thrd_sleep;^::time;^::timespec_get;^::tmpfile;^::tmpfile_s;^::tmpnam;^::tmpnam_s;^::tss_create;^::tss_get;^::tss_set;^::ungetc;^::ungetwc;^::vfprintf;^::vfprintf_s;^::vfscanf;^::vfscanf_s;^::vfwprintf;^::vfwprintf_s;^::vfwscanf;^::vfwscanf_s;^::vprintf_s;^::vscanf;^::vscanf_s;^::vsnprintf;^::vsnprintf_s;^::vsprintf;^::vsprintf_s;^::vsscanf;^::vsscanf_s;^::vswprintf;^::vswprintf_s;^::vswscanf;^::vswscanf_s;^::vwprintf_s;^::vwscanf;^::vwscanf_s;^::wcrtomb;^::wcschr;^::wcsftime;^::wcspbrk;^::wcsrchr;^::wcsrtombs;^::wcsrtombs_s;^::wcsstr;^::wcstod;^::wcstof;^::wcstoimax;^::wcstok;^::wcstok_s;^::wcstol;^::wcstold;^::wcstoll;^::wcstombs;^::wcstombs_s;^::wcstoul;^::wcstoull;^::wcstoumax;^::wcsxfrm;^::wctob;^::wctrans;^::wctype;^::wmemchr;^::wprintf_s;^::wscanf;^::wscanf_s;' + cert-oop54-cpp.WarnOnlyIfThisHasSuspiciousField: 'false' + cert-str34-c.DiagnoseSignedUnsignedCharComparisons: 'false' + cppcoreguidelines-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic: 'true' + google-readability-braces-around-statements.ShortStatementLines: '1' + google-readability-function-size.StatementThreshold: '800' + google-readability-namespace-comments.ShortNamespaceLines: '10' + google-readability-namespace-comments.SpacesBeforeComments: '2' + llvm-else-after-return.WarnOnConditionVariables: 'false' + llvm-else-after-return.WarnOnUnfixable: 'false' + llvm-qualified-auto.AddConstToQualified: 'false' +SystemHeaders: false +... diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..a7b1138 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,122 @@ +# Copyright (c) 2018-2025 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) + +# CI is quite simple at the moment. It will be improved and made more +# comprehensive, probably similar to Boost.Unordered. + +name: CI + +env: + BUILD_TYPE: Debug Release + +permissions: + contents: 'read' + pages: 'write' + id-token: 'write' + +on: [push, pull_request, workflow_dispatch] + +jobs: + Ubuntu: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + config: [Debug, Release] + compiler: [clang++, g++] + steps: + - uses: actions/checkout@v4 + - name: Install clang++ + run: | + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh $1 + if: matrix.compiler == 'clang++' + - name: Install Boost + run: sudo apt-get install -y libboost-all-dev + - name: Configure + run: cmake -DCMAKE_BUILD_TYPE=${{ matrix.config }} -DCMAKE_CXX_COMPILER=${{ matrix.compiler }} -DBUILD_TESTING=1 -DBUILD_EXAMPLES=1 -Bbuild + - name: Build + run: cmake --build build + - name: Test + run: | + ctest --test-dir build --rerun-failed --output-on-failure + Windows: + runs-on: windows-latest + strategy: + fail-fast: false + matrix: + config: [Debug, Release] + steps: + - uses: actions/checkout@v3 + - uses: ilammy/msvc-dev-cmd@v1 + - name: Install boost + uses: MarkusJx/install-boost@v2 + id: install-boost + with: + boost_version: 1.87.0 + - name: Configure + run: | + cmake -DCMAKE_BUILD_TYPE=${{ matrix.config }} -DBUILD_TESTING=1 -DBUILD_EXAMPLES=1 -Bbuild -DBoost_DIR=${{ steps.install-boost.outputs.BOOST_ROOT }} -DBoost_INCLUDE_DIR=${{steps.install-boost.outputs.BOOST_ROOT}}\include -DBoost_LIBRARY_DIRS=${{steps.install-boost.outputs.BOOST_ROOT}}\lib + env: + BOOST_ROOT: ${{ steps.install-boost.outputs.BOOST_ROOT }} + - name: Build + run: cmake --build build --config ${{ matrix.config }} + - name: Test + run: | + ctest --test-dir build --rerun-failed --output-on-failure -C ${{ matrix.config }} + MacOS: + runs-on: macos-latest + strategy: + fail-fast: false + matrix: + config: [Debug, Release] + steps: + - uses: actions/checkout@v3 + - uses: ilammy/msvc-dev-cmd@v1 + - name: Install boost + run: brew install boost + - name: Configure + run: | + cmake -DCMAKE_BUILD_TYPE=${{ matrix.config }} -DBUILD_TESTING=1 -DBUILD_EXAMPLES=1 -Bbuild + - name: Build + run: | + cmake --build build + - name: Test + run: | + ctest --test-dir build --rerun-failed --output-on-failure + Artifacts: + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - uses: actions/checkout@v4 + - name: Install asciidoctor + run: | + sudo apt-get install asciidoctor + asciidoctor doc/openmethod.adoc -o build_outputs_folder/index.html + - name: Generate documentation + run: | + sudo apt-get install asciidoctor + asciidoctor doc/openmethod.adoc -o build_outputs_folder/index.html + - name: Build flat headers + run: | + mkdir -p build_outputs_folder/boost/openmethod + python3 dev/flatten.py \ + build_outputs_folder/boost/openmethod.hpp \ + include/boost/openmethod.hpp \ + include/boost/openmethod/unique_ptr.hpp \ + include/boost/openmethod/shared_ptr.hpp \ + include/boost/openmethod/compiler.hpp + python3 dev/flatten.py \ + build_outputs_folder/boost/openmethod/policies.hpp \ + include/boost/openmethod/policies.hpp + - name: Upload static files as artifact + uses: actions/upload-pages-artifact@v3 # or specific "vX.X.X" version tag for this action + with: + path: build_outputs_folder/ + - name: Deploy to GitHub Pages + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/probe.yml b/.github/workflows/probe.yml new file mode 100644 index 0000000..c680d0a --- /dev/null +++ b/.github/workflows/probe.yml @@ -0,0 +1,15 @@ +name: Probe Workflow + +on: + workflow_dispatch: + +jobs: + probe: + runs-on: ubuntu-latest + + steps: + - name: List available compilers + run: | + lsb_release -a + apt list --installed | grep clang + apt list --installed | grep g++ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b4d4d7c --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +.hrefs +.rtags +build/ +builds/ +**/#*# +dependencies/* +extern/* +tests/benchmarks_parameters.hpp +**/vcpkg_installed +.venv/ + +build/* +__build__/* + +# ides +.vscode/ +.vs/ +CMakeSettings.json + +_deps/ + +export/ + +fetch_and_include/ + +tmpinst/ + +CMakeFiles/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..e69de29 diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..3f1523b --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,45 @@ +# Copyright (c) 2018-2025 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) + +cmake_minimum_required(VERSION 3.10) + +if(POLICY CMP0167) + cmake_policy(SET CMP0074 NEW) +endif() + +if(POLICY CMP0167) + cmake_policy(SET CMP0167 NEW) +endif() + +project(boost_openmethod VERSION 1.87.0 LANGUAGES CXX) + +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 17) +endif() +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +add_library(boost_openmethod INTERFACE) +add_library(Boost::openmethod ALIAS boost_openmethod) + +target_include_directories(boost_openmethod INTERFACE include) + +find_package(Boost COMPONENTS) +target_link_libraries(boost_openmethod INTERFACE Boost::boost) + +if(MSVC) + add_compile_options(/EHsc /FAs) +endif() + +if(BUILD_TESTING) + message(STATUS "Building tests") + include(CTest) + enable_testing() + add_subdirectory(test) +endif() + +if(BUILD_EXAMPLES) + message(STATUS "Building examples") + add_subdirectory(examples) +endif() diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..9ce89bf --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1 @@ +* @jll63 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..36b7cd9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,23 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..42fa508 --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# Boost.OpenMethod + +THIS IS NOT A BOOST LIBRARY (yet). + +The content of this repository is derived from YOMM2. It has been adapted to +Boost naming conventions for the purpose of being reviewed for inclusion in the +Boost C++ libraries. + +The documentation is [here](https://jll63.github.io/Boost.OpenMethod/). + +You can experiment with the library on Compiler Explorer by including +``. It also +includes the headers for the compiler, `shared_ptr` and `unique_ptr`. For +example, here is the last iteration of the [AST +example](https://godbolt.org/z/cPjzfanc8) from the tutorial. Don't forget to +turn on optimizations (`-O2 -DNDEBUG`) and to select the Boost library. diff --git a/ce/2-method-vptr-final.cpp b/ce/2-method-vptr-final.cpp new file mode 100644 index 0000000..d960c3e --- /dev/null +++ b/ce/2-method-vptr-final.cpp @@ -0,0 +1,68 @@ +#include +#include +#include +#include + +struct Animal { + const char* name; + Animal(const char* name) : name(name) { + } +}; + +struct Dog : Animal { + using Animal::Animal; +}; + +struct Cat : Animal { + using Animal::Animal; +}; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat); + +using boost::openmethod::virtual_ptr; + +BOOST_OPENMETHOD( + meet, (virtual_ptr, virtual_ptr, std::ostream&), void); + +BOOST_OPENMETHOD_OVERRIDE( + meet, (virtual_ptr a1, virtual_ptr a2, std::ostream& os), void) { + os << a1->name << " ignores " << a2->name << "\n"; +} + +BOOST_OPENMETHOD_OVERRIDE( + meet, (virtual_ptr a1, virtual_ptr a2, std::ostream& os), void) { + os << a1->name << " chases " << a2->name << "\n"; +} + +BOOST_OPENMETHOD_OVERRIDE( + meet, (virtual_ptr a1, virtual_ptr a2, std::ostream& os), void) { + os << a1->name << " runs away from " << a2->name << "\n"; +} + +BOOST_OPENMETHOD_OVERRIDE( + meet, (virtual_ptr a1, virtual_ptr a2, std::ostream& os), void) { + os << a1->name << " wags tail at " << a2->name << "\n"; +} + +void meet_animals( + const std::vector>& animals, std::ostream& os) { + for (auto animal : animals) { + for (auto other : animals) { + if (&animal != &other) { + meet(animal, other, os); + } + } + } +} + +int main() { + boost::openmethod::initialize(); + + Dog hector{"Hector"}, snoopy{"Snoopy"}; + Cat felix{"Felix"}, sylvester{"Sylvester"}; + std::vector> animals = { + virtual_ptr::final(hector), virtual_ptr::final(felix), + virtual_ptr::final(sylvester), virtual_ptr::final(snoopy)}; + + meet_animals(animals, std::cout); +} diff --git a/ce/2-method-vptr.cpp b/ce/2-method-vptr.cpp new file mode 100644 index 0000000..8e79872 --- /dev/null +++ b/ce/2-method-vptr.cpp @@ -0,0 +1,69 @@ +#include +#include +#include +#include + +struct Animal { + const char* name; + Animal(const char* name) : name(name) { + } + virtual ~Animal() { + } +}; + +struct Dog : Animal { + using Animal::Animal; +}; + +struct Cat : Animal { + using Animal::Animal; +}; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat); + +using boost::openmethod::virtual_ptr; + +BOOST_OPENMETHOD( + meet, (virtual_ptr, virtual_ptr, std::ostream&), void); + +BOOST_OPENMETHOD_OVERRIDE( + meet, (virtual_ptr a1, virtual_ptr a2, std::ostream& os), void) { + os << a1->name << " ignores " << a2->name << "\n"; +} + +BOOST_OPENMETHOD_OVERRIDE( + meet, (virtual_ptr a1, virtual_ptr a2, std::ostream& os), void) { + os << a1->name << " chases " << a2->name << "\n"; +} + +BOOST_OPENMETHOD_OVERRIDE( + meet, (virtual_ptr a1, virtual_ptr a2, std::ostream& os), void) { + os << a1->name << " runs away from " << a2->name << "\n"; +} + +BOOST_OPENMETHOD_OVERRIDE( + meet, (virtual_ptr a1, virtual_ptr a2, std::ostream& os), void) { + os << a1->name << " wags tail at " << a2->name << "\n"; +} + +void meet_animals( + const std::vector>& animals, std::ostream& os) { + for (auto animal : animals) { + for (auto other : animals) { + if (&animal != &other) { + meet(animal, other, os); + } + } + } +} + +int main() { + boost::openmethod::initialize(); + + Dog hector{"Hector"}, snoopy{"Snoopy"}; + Cat felix{"Felix"}, sylvester{"Sylvester"}; + std::vector> animals = { + hector, felix, sylvester, snoopy}; + + meet_animals(animals, std::cout); +} diff --git a/ce/2-method.cpp b/ce/2-method.cpp new file mode 100644 index 0000000..1669734 --- /dev/null +++ b/ce/2-method.cpp @@ -0,0 +1,61 @@ +#include +#include +#include +#include + +struct Animal { + const char* name; + Animal(const char* name) : name(name) { + } + virtual ~Animal() { + } +}; + +struct Dog : Animal { + using Animal::Animal; +}; + +struct Cat : Animal { + using Animal::Animal; +}; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat); + +BOOST_OPENMETHOD( + meet, (virtual_, virtual_, std::ostream&), void); + +BOOST_OPENMETHOD_OVERRIDE(meet, (Cat & a1, Cat& a2, std::ostream& os), void) { + os << a1.name << " ignores " << a2.name << "\n"; +} + +BOOST_OPENMETHOD_OVERRIDE(meet, (Dog & a1, Cat& a2, std::ostream& os), void) { + os << a1.name << " chases " << a2.name << "\n"; +} + +BOOST_OPENMETHOD_OVERRIDE(meet, (Cat & a1, Dog& a2, std::ostream& os), void) { + os << a1.name << " runs away from " << a2.name << "\n"; +} + +BOOST_OPENMETHOD_OVERRIDE(meet, (Dog & a1, Dog& a2, std::ostream& os), void) { + os << a1.name << " wags tail at " << a2.name << "\n"; +} + +void meet_animals(const std::vector& animals, std::ostream& os) { + for (auto animal : animals) { + for (auto other : animals) { + if (&animal != &other) { + meet(*animal, *other, os); + } + } + } +} + +int main() { + boost::openmethod::initialize(); + + Dog hector{"Hector"}, snoopy{"Snoopy"}; + Cat felix{"Felix"}, sylvester{"Sylvester"}; + std::vector animals = {&hector, &felix, &sylvester, &snoopy}; + + meet_animals(animals, std::cout); +} diff --git a/ce/CMakeLists.txt b/ce/CMakeLists.txt new file mode 100644 index 0000000..0d0ccc6 --- /dev/null +++ b/ce/CMakeLists.txt @@ -0,0 +1,28 @@ +# Copyright (c) 2018-2024 Jean-Louis Leroy +# Distributed under the Boost Software License, Version 1.0. +# See accompanying filce_e LICENSE_1_0.txt +# or copy at hce_ttp://www.boost.oce_rg/LICENSE_1_0.txt) + +add_executable(ce_virtual virtual.cpp) +add_test(NAME ce_virtual COMMAND ce_virtual) + +add_executable(ce_uni-method uni-method.cpp) +add_test(NAME ce_uni-method COMMAND ce_uni-method) + +add_executable(ce_uni-method-vptr uni-method-vptr.cpp) +add_test(NAME ce_uni-method-vptr COMMAND ce_uni-method-vptr) + +add_executable(ce_virtual-double virtual-double.cpp) +add_test(NAME ce_virtual-double COMMAND ce_virtual-double) + +add_executable(ce_2-method 2-method.cpp) +add_test(NAME ce_2-method COMMAND ce_2-method) + +add_executable(ce_2-method-vptr 2-method-vptr.cpp) +add_test(NAME ce_2-method-vptr COMMAND ce_2-method-vptr) + +add_executable(ce_2-method-vptr-final 2-method-vptr-final.cpp) +add_test(NAME ce_2-method-vptr-fince_al COMMAND ce_2-method-vptr-final) + +add_executable(ce_uni-method-vptr-final uni-method-vptr-final.cpp) +add_test(NAME ce_uni-method-vptr-fce_inal COMMAND ce_uni-method-vptr-final) diff --git a/ce/README.md b/ce/README.md new file mode 100644 index 0000000..46c9a73 --- /dev/null +++ b/ce/README.md @@ -0,0 +1,26 @@ +# YOMM2 on Compiler Explorer + +YOMM2 is available on Compiler Explorer. Make sure that you also select Boost +version 1.74 or above, and you probably want to add the `-O3 -DNDEBUG` compiler +switches. + +The following examples are available: + +* The [examples](https://jll63.github.io/yomm2/ce/slides.html) from the slides. +* The matrix example from the GitHub langing page. + +The following examples use the diff mode to compare open methods with the +equivalent (closed) virtual function based approaches. + +* [virtual function call vs uni-method call via plain reference](https://jll63.github.io/yomm2/ce/vf-vs-1m-ref.html) +* [virtual function call vs uni-method call via virtual_ptr ](https://jll63.github.io/yomm2/ce/vf-vs-1m-vptr.html) +* [double dispatch vs multi-method call via plain reference](https://jll63.github.io/yomm2/ce/2d-vs-2m-ref.html) +* [double dispatch vs multi-method call via virtual_ptr ](https://jll63.github.io/yomm2/ce/2d-vs-2m-vptr.html) + +YOMM2 can also [add polymorphic operations to non-polymorphic +classes](https://jll63.github.io/yomm2/ce/vptr-final.html). + +When `virtual_ptr` is used in combination with generated static offsets, method +dispatch matches the speed of virtual functions. It is also possible to generate +dispatch data that can be installed without calling `update`, a fairly expensive +operaiton. See [this example](https://jll63.github.io/yomm2/ce/generator.html). diff --git a/ce/uni-method-vptr-final.cpp b/ce/uni-method-vptr-final.cpp new file mode 100644 index 0000000..73b0a99 --- /dev/null +++ b/ce/uni-method-vptr-final.cpp @@ -0,0 +1,55 @@ +#include +#include + +#include +#include + +struct Animal { + const char* name; + Animal(const char* name) : name(name) { + } +}; + +struct Dog : Animal { + using Animal::Animal; +}; + +struct Cat : Animal { + using Animal::Animal; +}; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat); + +using boost::openmethod::final_virtual_ptr; +using boost::openmethod::virtual_ptr; + +BOOST_OPENMETHOD(poke, (virtual_ptr, std::ostream&), void); + +BOOST_OPENMETHOD_OVERRIDE( + poke, (virtual_ptr animal, std::ostream& os), void) { + os << animal->name << " hisses.\n"; +} + +BOOST_OPENMETHOD_OVERRIDE( + poke, (virtual_ptr animal, std::ostream& os), void) { + os << animal->name << " barks.\n"; +} + +void poke_animals( + const std::vector>& animals, std::ostream& os) { + for (auto animal : animals) { + poke(animal, os); + } +} + +int main() { + boost::openmethod::initialize(); + + Dog hector{"Hector"}, snoopy{"Snoopy"}; + Cat felix{"Felix"}, sylvester{"Sylvester"}; + std::vector> animals = { + final_virtual_ptr(hector), virtual_ptr::final(felix), + final_virtual_ptr(sylvester), virtual_ptr::final(snoopy)}; + + poke_animals(animals, std::cout); +} diff --git a/ce/uni-method-vptr.cpp b/ce/uni-method-vptr.cpp new file mode 100644 index 0000000..b5b8a94 --- /dev/null +++ b/ce/uni-method-vptr.cpp @@ -0,0 +1,54 @@ +#include +#include +#include +#include + +struct Animal { + const char* name; + Animal(const char* name) : name(name) { + } + virtual ~Animal() { + } +}; + +struct Dog : Animal { + using Animal::Animal; +}; + +struct Cat : Animal { + using Animal::Animal; +}; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat); + +using boost::openmethod::virtual_ptr; + +BOOST_OPENMETHOD(poke, (virtual_ptr, std::ostream&), void); + +BOOST_OPENMETHOD_OVERRIDE( + poke, (virtual_ptr animal, std::ostream& os), void) { + os << animal->name << " hisses.\n"; +} + +BOOST_OPENMETHOD_OVERRIDE( + poke, (virtual_ptr animal, std::ostream& os), void) { + os << animal->name << " barks.\n"; +} + +void poke_animals( + const std::vector>& animals, std::ostream& os) { + for (auto animal : animals) { + poke(animal, os); + } +} + +int main() { + boost::openmethod::initialize(); + + Dog hector{"Hector"}, snoopy{"Snoopy"}; + Cat felix{"Felix"}, sylvester{"Sylvester"}; + std::vector> animals = { + hector, felix, sylvester, snoopy}; + + poke_animals(animals, std::cout); +} diff --git a/ce/uni-method.cpp b/ce/uni-method.cpp new file mode 100644 index 0000000..1ba6bcd --- /dev/null +++ b/ce/uni-method.cpp @@ -0,0 +1,48 @@ +#include +#include +#include +#include + +struct Animal { + const char* name; + Animal(const char* name) : name(name) { + } + virtual ~Animal() { + } +}; + +struct Dog : Animal { + using Animal::Animal; +}; + +struct Cat : Animal { + using Animal::Animal; +}; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat); + +BOOST_OPENMETHOD(poke, (virtual_, std::ostream&), void); + +BOOST_OPENMETHOD_OVERRIDE(poke, (Cat & animal, std::ostream& os), void) { + os << animal.name << " hisses.\n"; +} + +BOOST_OPENMETHOD_OVERRIDE(poke, (Dog & animal, std::ostream& os), void) { + os << animal.name << " barks.\n"; +} + +void poke_animals(const std::vector& animals, std::ostream& os) { + for (auto animal : animals) { + poke(*animal, os); + } +} + +int main() { + boost::openmethod::initialize(); + + Dog hector{"Hector"}, snoopy{"Snoopy"}; + Cat felix{"Felix"}, sylvester{"Sylvester"}; + std::vector animals = {&hector, &felix, &sylvester, &snoopy}; + + poke_animals(animals, std::cout); +} diff --git a/ce/virtual-double.cpp b/ce/virtual-double.cpp new file mode 100644 index 0000000..8722414 --- /dev/null +++ b/ce/virtual-double.cpp @@ -0,0 +1,69 @@ +#include +#include + +struct Cat; +struct Dog; + +struct Animal { + const char* name; + Animal(const char* name) : name(name) { + } + virtual ~Animal() { + } + virtual void meet(Animal& other, std::ostream& os) = 0; + virtual void meet_cat(Cat& other, std::ostream& os) = 0; + virtual void meet_dog(Dog& other, std::ostream& os) = 0; +}; + +struct Cat : Animal { + using Animal::Animal; + void meet(Animal& other, std::ostream& os) override; + void meet_cat(Cat& other, std::ostream& os) override; + void meet_dog(Dog& other, std::ostream& os) override; +}; + +struct Dog : Animal { + using Animal::Animal; + void meet(Animal& other, std::ostream& os) override; + void meet_cat(Cat& other, std::ostream& os) override; + void meet_dog(Dog& other, std::ostream& os) override; +}; + +void meet_animals(const std::vector& animals, std::ostream& os) { + for (auto animal : animals) { + for (auto other : animals) { + if (animal != other) { + animal->meet(*other, os); + } + } + } +} + +int main() { + Dog hector{"Hector"}, snoopy{"Snoopy"}; + Cat felix{"Felix"}, sylvester{"Sylvester"}; + std::vector animals = {&hector, &felix, &sylvester, &snoopy}; + meet_animals(animals, std::cout); +} + +void Cat::meet(Animal& other, std::ostream& os) { + other.meet_cat(*this, os); +} + +void Cat::meet_cat(Cat& other, std::ostream& os) { + os << name << " ignores " << other.name << "\n"; +} + +void Cat::meet_dog(Dog& other, std::ostream& os) { + os << name << " runs away from " << other.name << "\n"; +} + +void Dog::meet(Animal& other, std::ostream& os) { + other.meet_dog(*this, os); +} +void Dog::meet_cat(Cat& other, std::ostream& os) { + os << name << " chases " << other.name << "\n"; +} +void Dog::meet_dog(Dog& other, std::ostream& os) { + os << name << " wags tail at " << other.name << "\n"; +} diff --git a/ce/virtual.cpp b/ce/virtual.cpp new file mode 100644 index 0000000..7287eea --- /dev/null +++ b/ce/virtual.cpp @@ -0,0 +1,38 @@ +#include +#include + +struct Animal { + const char* name; + Animal(const char* name) : name(name) { + } + virtual ~Animal() { + } + virtual void poke(std::ostream& os) = 0; +}; + +struct Dog : Animal { + using Animal::Animal; + void poke(std::ostream& os) override { + os << name << " barks.\n"; + } +}; + +struct Cat : Animal { + using Animal::Animal; + void poke(std::ostream& os) override { + os << name << " hisses.\n"; + } +}; + +void poke_animals(const std::vector& animals, std::ostream& os) { + for (auto animal : animals) { + animal->poke(os); + } +} + +int main() { + Dog hector{"Hector"}, snoopy{"Snoopy"}; + Cat felix{"Felix"}, sylvester{"Sylvester"}; + std::vector animals = {&hector, &felix, &sylvester, &snoopy}; + poke_animals(animals, std::cout); +} diff --git a/clang-llvm b/clang-llvm new file mode 100644 index 0000000..33bf2a3 --- /dev/null +++ b/clang-llvm @@ -0,0 +1,137 @@ +--- +Language: Cpp +# BasedOnStyle: LLVM +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignConsecutiveMacros: false +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Right +AlignOperands: true +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DeriveLineEnding: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + SortPriority: 0 + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + SortPriority: 0 + - Regex: '.*' + Priority: 1 + SortPriority: 0 +IncludeIsMainRegex: '(Test)?$' +IncludeIsMainSourceRegex: '' +IndentCaseLabels: false +IndentGotoLabels: true +IndentPPDirectives: None +IndentWidth: 2 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Right +ReflowComments: true +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInConditionalStatement: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceBeforeSquareBrackets: false +Standard: Latest +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 8 +UseCRLF: false +UseTab: Never +... + diff --git a/dev/ci-pre-configure-clang b/dev/ci-pre-configure-clang new file mode 100755 index 0000000..5855010 --- /dev/null +++ b/dev/ci-pre-configure-clang @@ -0,0 +1,5 @@ +#!/bin/bash + +wget https://apt.llvm.org/llvm.sh +chmod +x llvm.sh +sudo ./llvm.sh $1 diff --git a/dev/flatten.py b/dev/flatten.py new file mode 100644 index 0000000..647f171 --- /dev/null +++ b/dev/flatten.py @@ -0,0 +1,40 @@ +#!/usr/bin/python3 + +import argparse +from pathlib import Path +import re + +parser = argparse.ArgumentParser() +parser.add_argument('output', type=Path) +parser.add_argument('input', nargs="+", type=Path) +args = parser.parse_args() + +prefix = args.input[0].absolute() + +while prefix.name != 'boost': + assert prefix.parent != prefix + prefix = prefix.parent + +prefix = prefix.parent +skip = len(str(prefix)) + 1 + +def flatten(input, output, done): + header = str(input)[skip:] + if header in done: + return + done.add(header) + with input.open() as ifh: + for line in ifh: + if m := re.match(r"#include <(boost/openmethod/[^>]+)>", line): + include = m[1] + print(file=output) + flatten(prefix / include, output, done) + print(file=output) + else: + output.write(line) + + +with args.output.open('w') as ofh: + done = set() + for input in args.input: + flatten(input.absolute(), ofh, done) diff --git a/dev/reformat b/dev/reformat new file mode 100755 index 0000000..0910d91 --- /dev/null +++ b/dev/reformat @@ -0,0 +1,8 @@ +#!/bin/bash + +cd "$(dirname $0)/.." + +srcs=$(find include examples test \ + -name '*.?pp' | grep -v cmake_fetchcontent | grep -v /CMakeFiles) + +clang-format -i --verbose $srcs diff --git a/dev/v2-boost.py b/dev/v2-boost.py new file mode 100644 index 0000000..57acea1 --- /dev/null +++ b/dev/v2-boost.py @@ -0,0 +1,98 @@ +#!/usr/bin/python3 + +import os +from pathlib import Path +import re +from subprocess import check_call +import subprocess +import sys + +BOOST_NAME = "openmethod" +BOOST_INCLUDE = Path("include", "boost", BOOST_NAME) +BOOST_OPENMETHOD_PATH = Path.home().joinpath("dev", "boost", "libs", BOOST_NAME) +YOMM2_V2_PATH = Path.home().joinpath("dev", "yomm2") +YOMM2_DIRS = "include/yorel/yomm2 tests examples ce".split() +SKIP = [ + "cute", + "keywords", + "benchmark", + "pss1", + "lab", + "manual_call", + "generator", + "containers", + "conan", + "vcpkg", + "README", + "cmakeyomm2", + "templates", +] +REPLACE = ( + ("YOREL_YOMM2_", f"BOOST_{BOOST_NAME.upper()}_"), + ("YOMM2_", f"BOOST_{BOOST_NAME.upper()}_"), + ("yOMM2_", f"BOOST_{BOOST_NAME.upper()}_DETAIL_"), + ("BOOST_OPENMETHOD_METHOD", "BOOST_OPENMETHOD"), + ("#include ", ""), + ("yorel", "boost"), + ("yomm2", "openmethod"), + ("YOMM2_DECLARE|declare_method", "BOOST_OPENMETHOD"), + ("YOMM2_DEFINE|define_method", "BOOST_OPENMETHOD_OVERRIDE"), + ("YOMM2_CLASS(ES)?|register_class(es)?", "BOOST_OPENMETHOD_CLASSES"), +) + +git = f"git diff --name-only -- boost test examples" +print(git) + +modified = subprocess.check_output(git.split(), encoding="ascii") + +if modified: + sys.exit("Unstaged changes:\n" + modified) + +git = f"git -C {YOMM2_V2_PATH} rev-parse --abbrev-ref HEAD" +print(git) + +version = subprocess.check_output(git.split(), encoding="ascii").strip() + +if version != "v2": + sys.exit(f"Source directory is at version {version}, v2 is required.\n") + + +def skip(path): + for skip in SKIP: + if skip in str(path): + return True + + return False + + +for dir in YOMM2_DIRS: + for from_path in (YOMM2_V2_PATH / dir).rglob("*.?pp"): + if skip(from_path): + continue + + with from_path.open() as f: + content = f.read() + + content = re.sub(r'#include "(yorel/yomm2/[^"])+"', r"#include <\1>", content) + + for replace in REPLACE: + content = re.sub(*replace, content) + + to_path = Path( + str(from_path) + .replace(str(YOMM2_V2_PATH), str(BOOST_OPENMETHOD_PATH)) + .replace("yorel/yomm2", f"boost/{BOOST_NAME}") + .replace("tests/", "test/") + ) + + print(f"{from_path} -> {to_path}...") + + to_path.parent.mkdir(parents=True, exist_ok=True) + + with to_path.open("w") as f: + f.write(content) + + check_call(["clang-format", "-i", str(to_path)]) + +print("done") diff --git a/dev/yomm2filt b/dev/yomm2filt new file mode 100755 index 0000000..b6f0d19 --- /dev/null +++ b/dev/yomm2filt @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 + +import argparse +import fileinput +import re +from subprocess import Popen, PIPE +import threading + +parser = argparse.ArgumentParser() +parser.add_argument("-s", "--scope", help="do not simplify scopes") +parser.add_argument("-y", "--prefix", help="do not remove YOMM2 prefixes") +parser.add_argument("-p", "--policy", help="do not remove policy") +parser.add_argument("files", nargs="*", default=("-")) +args = parser.parse_args() + +def process(fh): + while True: + line = fh.readline() + if not line: + return + if not args.prefix: + line = re.sub(r"\s*,\s*yorel::yomm2::policy::\w+", "", line) + if not args.scope: + line = re.sub(r"(\w+::)+", "", line) + if not args.prefix: + line = re.sub(r"YoMm2_S_", "", line) + print(line, end="") + +i = 0 + +with Popen( + "/usr/bin/c++filt --types".split(), stdin=PIPE, stdout=PIPE, encoding="ascii" +) as cppfilt: + demangler = threading.Thread(target=process, args=[cppfilt.stdout]) + demangler.start() + for line in fileinput.input(files=args.files): + print(line, file=cppfilt.stdin, end="") + cppfilt.stdin.close() + demangler.join() \ No newline at end of file diff --git a/doc/BOOST_OPENMETHOD.adoc b/doc/BOOST_OPENMETHOD.adoc new file mode 100644 index 0000000..8394684 --- /dev/null +++ b/doc/BOOST_OPENMETHOD.adoc @@ -0,0 +1,43 @@ + +[#BOOST_OPENMETHOD] + +## BOOST_OPENMETHOD + +### Synopsis + +Defined in . + +```c++ +BOOST_OPENMETHOD(NAME, (PARAMETERS...), RETURN_TYPE [, POLICY]); +``` + +### Description + +Declares a method. + +### Effects + +The macro expands to several constructs: + +* A `struct` forward declaration that acts as the method's identifier: + +```c++ +struct BOOST_OPENMETHOD_NAME(NAME); +``` + +* An inline function template, constrained to take the same `PARAMETERS`, + without the `virtual_` decorators, returning a `RETURN_TYPE`. The function + forwards to + + `method::fn`. + +* A guide function used to match overriders with the method: + +```c++ +auto BOOST_OPENMETHOD_NAME(NAME)_guide(...) + -> ::boost::openmethod::method< + BOOST_OPENMETHOD_NAME(NAME)(PARAMETERS...), RETURN_TYPE [, POLICY]>; +``` + +NOTE: The default value for `POLICY` is the value of +`BOOST_OPENMETHOD_DEFAULT_POLICY` at the point `` is +included. Changing the value of this symbol has no effect after that point. diff --git a/doc/BOOST_OPENMETHOD_CLASSES.adoc b/doc/BOOST_OPENMETHOD_CLASSES.adoc new file mode 100644 index 0000000..91798de --- /dev/null +++ b/doc/BOOST_OPENMETHOD_CLASSES.adoc @@ -0,0 +1,23 @@ + +[#BOOST_OPENMETHOD_CLASSES] + +## BOOST_OPENMETHOD_CLASSES + +### Synopsis + +Defined in . + +```c++ +BOOST_OPENMETHOD_CLASSES(CLASSES...[, POLICY]); +``` + +### Description + +Register `CLASSES` in POLICY. + +NOTE: The default value for `POLICY` is the value of +`BOOST_OPENMETHOD_DEFAULT_POLICY` when `` is +included. Subsequently changing it has no retroactive effect. + +This macro is a wrapper around `use_classes`; see its documentation for more +details. diff --git a/doc/BOOST_OPENMETHOD_DEFAULT_POLICY.adoc b/doc/BOOST_OPENMETHOD_DEFAULT_POLICY.adoc new file mode 100644 index 0000000..4400b25 --- /dev/null +++ b/doc/BOOST_OPENMETHOD_DEFAULT_POLICY.adoc @@ -0,0 +1,22 @@ + +[#BOOST_OPENMETHOD_DEFAULT_POLICY] + +## BOOST_OPENMETHOD_DEFAULT_POLICY + +### Description + +The name of the default policy. + +`BOOST_OPENMETHOD_DEFAULT_POLICY` is the default value for the `Policy` template +parameter of `method`, `use_classes`, and other constructs defined in +``. If it is not defined, +`::boost::openmethod::policy::default_policy` is used. + +`BOOST_OPENMETHOD_DEFAULT_POLICY` can be defined by a program to change the +default policy globally. Once `` has been included, +redefining the symbol has no effect. To override the default policy, proceed as +follows: + +1. Include headers under `boost/openmethod/policies/` as needed. +2. Create a policy class, and set `BOOST_OPENMETHOD_DEFAULT_POLICY`. +3. Include ``. diff --git a/doc/BOOST_OPENMETHOD_INLINE_OVERRIDE.adoc b/doc/BOOST_OPENMETHOD_INLINE_OVERRIDE.adoc new file mode 100644 index 0000000..c3b9eac --- /dev/null +++ b/doc/BOOST_OPENMETHOD_INLINE_OVERRIDE.adoc @@ -0,0 +1,19 @@ + +[#BOOST_OPENMETHOD_INLINE_OVERRIDE] + +## BOOST_OPENMETHOD_INLINE_OVERRIDE + +### Synopsis + +Defined in . + +```c++ +BOOST_OPENMETHOD_INLINE_OVERRIDE(NAME, (PARAMETERS...), RETURN_TYPE) { + // body +} +``` + +### Description + +`BOOST_OPENMETHOD_INLINE_OVERRIDE` performs the same function as +`BOOST_OPENMETHOD_OVERRIDE`, except that the overrider is defined inline. diff --git a/doc/BOOST_OPENMETHOD_NAME.adoc b/doc/BOOST_OPENMETHOD_NAME.adoc new file mode 100644 index 0000000..3b95301 --- /dev/null +++ b/doc/BOOST_OPENMETHOD_NAME.adoc @@ -0,0 +1,17 @@ + +[#BOOST_OPENMETHOD_NAME] + +## BOOST_OPENMETHOD_NAME + +### Synopsis + +Defined in . + +```c++ +#define BOOST_OPENMETHOD_NAME(NAME) boost_openmethod_#NAME +``` + +### Description + +Generate a long name from a short name, hopefully avoiding conflicts with +user-defined names. diff --git a/doc/BOOST_OPENMETHOD_OVERRIDE.adoc b/doc/BOOST_OPENMETHOD_OVERRIDE.adoc new file mode 100644 index 0000000..025c2d4 --- /dev/null +++ b/doc/BOOST_OPENMETHOD_OVERRIDE.adoc @@ -0,0 +1,59 @@ + +[#BOOST_OPENMETHOD_OVERRIDE] + +## BOOST_OPENMETHOD_OVERRIDE + +### Synopsis + +Defined in . + +```c++ +BOOST_OPENMETHOD_OVERRIDE(NAME, (PARAMETERS...), RETURN_TYPE) { + // body +} +``` + +### Description + +`BOOST_OPENMETHOD_OVERRIDE` adds an overrider to a method. + +The method is deduced from a call to a method guide function with the +overrider's arguments. + +The macro creates several entities in the current scope. + +* A class template that acts as a container for the overriders of the methods +called `NAME`: + +```c++ +template BOOST_OPENMETHOD_OVERRIDERS(NAME); +``` + +* A specialization of the container template for the overrider: + +```c++ +struct BOOST_OPENMETHOD_OVERRIDERS(NAME) { + static auto fn(PARAMETERS...) -> RETURN_TYPE; + static auto has_next() -> bool; + template + static auto next(typename... Args) -> RETURN_TYPE; +}; +``` + +where: + +* `fn` is the overrider function. + +* `has_next()` returns `true` if a less specialized overrider exists. + +* `next(Args... args)` calls the next most specialized overrider via the +pointer stored in the method's `next` member variable. + +Finally, the macro starts the definition of the overrider function: + +```c++ +auto BOOST_OPENMETHOD_OVERRIDERS(NAME)::fn( + PARAMETERS...) -> RETURN_TYPE +``` + +The block following the call to the macro is the body of the function. diff --git a/doc/BOOST_OPENMETHOD_OVERRIDERS.adoc b/doc/BOOST_OPENMETHOD_OVERRIDERS.adoc new file mode 100644 index 0000000..4ec0367 --- /dev/null +++ b/doc/BOOST_OPENMETHOD_OVERRIDERS.adoc @@ -0,0 +1,16 @@ + +## BOOST_OPENMETHOD_OVERRIDERS + +### Synopsis + +Defined in . + +```c++ +#define BOOST_OPENMETHOD_OVERRIDERS(NAME) \ + BOOST_PP_CAT(BOOST_OPENMETHOD_NAME(NAME), _overriders) +``` + +### Description + +`BOOST_OPENMETHOD_OVERRIDERS` expands to the name of the class template that +contains the overriders for all the methods with a given name. diff --git a/doc/BOOST_OPENMETHOD_REGISTER.adoc b/doc/BOOST_OPENMETHOD_REGISTER.adoc new file mode 100644 index 0000000..9e174bf --- /dev/null +++ b/doc/BOOST_OPENMETHOD_REGISTER.adoc @@ -0,0 +1,16 @@ + +[#BOOST_OPENMETHOD_REGISTER] + +## BOOST_OPENMETHOD_REGISTER + +### Synopsis + +Defined in . + +```c++ +BOOST_OPENMETHOD_REGISTER(TYPE); +``` + +### Description + +Creates a static instance of `TYPE`, using a unique generated name. diff --git a/doc/Jamfile b/doc/Jamfile new file mode 100644 index 0000000..da01a05 --- /dev/null +++ b/doc/Jamfile @@ -0,0 +1,19 @@ +# Copyright 2018-2024 Peter Dimov +# +# 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) + +project doc/openmethod ; +using asciidoctor ; + +html index.html : openmethod.adoc : stylesheet=zajo-dark.css linkcss ; +install html_ : index.html skin.png zajo-dark.css zajo-light.css rouge-github.css : html ; + +# pdf openmethod.pdf : openmethod.adoc : book pdf-themesdir=. pdf-theme=openmethod ; +# install pdf_ : openmethod.pdf : html ; + +alias boostdoc ; +explicit boostdoc ; +alias boostrelease : html_ ; +explicit boostrelease ; diff --git a/doc/abstract_policy.adoc b/doc/abstract_policy.adoc new file mode 100644 index 0000000..b776882 --- /dev/null +++ b/doc/abstract_policy.adoc @@ -0,0 +1,18 @@ +## abstract_policy + +### Synopsis + +Defined in . + +```c++ +namespace boost::openmethod::policies { + +struct abstract_policy {}; + +} +``` +### Description + +`abstract_policy` is a required base class for a policy. It makes it possible +for meta-functions such as `use_classes` to discriminate between user classes +and the (optional) policy class. diff --git a/doc/basic_error_output.adoc b/doc/basic_error_output.adoc new file mode 100644 index 0000000..3c3ed25 --- /dev/null +++ b/doc/basic_error_output.adoc @@ -0,0 +1,34 @@ + +## basic_error_output + +### Synopsis + +Defined in . + +```c++ +namespace boost::openmethod::policies { + +template +struct basic_error_output : virtual error_output { + static Stream error_stream; +}; + +} +``` + +### Description + +`basic_error_output` is an implementation of `error_output` that writes error +messages to a `RestrictedOutputStream`. + +### Members + +#### error_stream + +```c++ +Stream error_stream; +``` + +Initialized by the default constructor of `Stream`. It is the responsibility of +the program to initializate it if needed, e.g., for a `std::ofstream`, to open +it. diff --git a/doc/basic_policy.adoc b/doc/basic_policy.adoc new file mode 100644 index 0000000..eb4e2d3 --- /dev/null +++ b/doc/basic_policy.adoc @@ -0,0 +1,144 @@ + +## basic_policy + +### Synopsis + +Defined in . + +```c++ +namespace boost::openmethod { + +namespace policies { + +template +struct basic_policy : virtual abstract_policy, + virtual domain, + virtual Facets... { + template + static constexpr bool has_facet = /*unspecified*/; + + template + using fork = /*unspecified*/; + + template + using add = /*unspecified*/; + + template + using replace = /*unspecified*/; + + template + using remove = /*unspecified*/; +}; + +struct release : basic_policy {}; + +struct debug : release::add<...> {}; + +} // policies + +#ifdef NDEBUG +using default_policy = policies::release; +#else +using default_policy = policies::debug; +#endif + +} // boost::openmethod +``` + +### Description + +`basic_policy` implements a policy, which consists of a a collection of methods, +classes, dispatch data, and facets, which specify how to obtain a pointer to a +v-table from an object, how to report errors, whether to perform runtime sanity +checks, etc. + +Some of these functionalities require static variables local to the policy. +Forthis reason, `basic_policy` uses the CRTP pattern to provide ensure that two +different policies - and the facets they contain - get their own copies of the +static state. + +### Members + +#### has_facet + +```c++ +template +static constexpr bool has_facet; +``` + +Evaluates to `true` if _Policy_ contains _Facet_. + +#### fork + +```c++ +template +using fork; +``` + +Creates a new policy from an existing one. _NewPolicy_, and the facets it +contains, do not share static variables with the original _Policy_. The new +policy does not retain any knowledge of the classes and methods registered in +the original. + +#### add + +```c++ +template +using add; +``` + +Creates a new policy by adding _MoreFacets_ to the original policy's collection +of facets. The original policy and the new one share static variables. + +#### replace + +```c++ +template +using replace; +``` + +Creates a new policy by replacing the facet in _Policy_ that derives from _Base_ +with _NewFacet_. It is not an error if _policy_ does not contain such a facet; +in that case, the new policy contains the same facet as the original one. + +The original policy and the new one share static variables. + +#### remove + +```c++ +template +using remove; +``` + +Creates a new policy by removing the facet in _Policy_ that derives from _Base_. +It is not an error if _policy_ does not contain such a facet; in that case, the +new policy contains the same facet as the original one. + +The original policy and the new one share static variables. + +### Non-members + +#### release + +```c++ +struct release; +``` + +A policy that contains facet implementations `std_rtti`, `fast_perfect_hash`, +`vptr_vector` and `vectored_error_handler`. + +#### debug + +```c++ +struct debug; +``` + +The `release` policy with additional facet implementations `runtime_checks`, +`basic_error_output` and basic_trace_output. + +NOTE: `debug` extends `release` but it does not a fork it. Both policies use the +same `domain`. + +#### default_policy + +An alias for `release` if `NDEBUG` is defined, and for `debug` otherwise. diff --git a/doc/basic_trace_output.adoc b/doc/basic_trace_output.adoc new file mode 100644 index 0000000..bb9fa51 --- /dev/null +++ b/doc/basic_trace_output.adoc @@ -0,0 +1,43 @@ + +## basic_trace_output + +### Synopsis + +Defined in . + +```c++ +namespace boost::openmethod::policies { + +template +struct basic_trace_output : virtual trace_output { + static bool trace_enabled; + static Stream trace_stream; +}; + +} +``` + +### Description + +`basic_error_output` is an implementation of `trace_output` that writes error +messages to a `RestrictedOutputStream`. + +### Members + +#### trace_enabled + +```c++ +static bool trace_enabled; +``` + +Set to `true` if environment variable `BOOST_OPENMETHOD_TRACE` is set to `1`. + +#### trace_stream + +```c++ +static Stream trace_stream; +``` + +Initialized by the default constructor of `Stream`. It is the responsibility of +the program to prepare it for output if needed, e.g., for a `std::ofstream`, to +open it. diff --git a/doc/core_api.adoc b/doc/core_api.adoc new file mode 100644 index 0000000..fb2eeee --- /dev/null +++ b/doc/core_api.adoc @@ -0,0 +1,92 @@ + +## Core API + +OpenMethod provides a macro-free interface: the core API. This is useful in +certain situations, for example when combining open-methods and templates. + +Let's rewrite the Animals example using the core API. An open-method is +implemented as an instance of the `method` template. Its parameters are a +function signature and a return type: + +[source,c++] +---- +#include + +using namespace boost::openmethod; + +class poke_openmethod; + +using poke = method< + poke_openmethod(std::ostream&, virtual_), void>; +---- + + +The `poke_openmethod` class acts as the method's identifier: it separates it +from other methods with the same signature. The exact name does not really +matter, and the class needs not be defined, only declared. Inventing a class +name can get tedious, so OpenMethod provides a macro for that: + + +[source,c++] +---- +include::{examplesdir}/core_api.cpp[tag=method] +---- + +NOTE: BOOST_OPENMETHOD and associated macros use `BOOST_OPENMETHOD_NAME` in +their implementation. This makes it possible to mix the "macro" and "core" +styles. + +We call the method via the nested function object `fn`: + +[source,c++] +---- +poke::fn(std::cout, animal); +---- + +Overriders are ordinary functions, added to a method using the nested template +`override`: + +[source,c++] +---- +include::{examplesdir}/core_api.cpp[tag=poke_cat] +---- + +NOTE: `override` can register multiple overriders. + +In C++26, we will be able to use `_` instead of inventing a one-time-use +identifier. In the meantime, OpenMethod provides a small convenience macro: + +[source,c++] +---- +include::{examplesdir}/core_api.cpp[tag=poke_dog] +---- + +`next` is available from the method's nested `next` template: + +[source,c++] +---- +include::{examplesdir}/core_api.cpp[tag=poke_bulldog] +---- + +NOTE: Since the function uses itself as a template argument in its body, its +return type cannot be deduced. It must be specified explicitly, either by using +the old function declaration style or a trailing return type. + + +Why not call `poke_dog` directly? We could; however, keep in mind that, in a +real program, a translation unit is not necessarily aware of the overriders +added elsewhere - especially in presence of dynamic loading. + +We register the classes with `use_classes`: + +[source,c++] +---- +include::{examplesdir}/core_api.cpp[tag=use_classes] +---- + +Finally, we call the method via the static member of the method class `fn`: + +[source,c++] +---- +include::{examplesdir}/core_api.cpp[tag=main] +---- diff --git a/doc/custom_rtti.adoc b/doc/custom_rtti.adoc new file mode 100644 index 0000000..0c83164 --- /dev/null +++ b/doc/custom_rtti.adoc @@ -0,0 +1,163 @@ + +## Custom RTTI + +Stock policies use the `std_rtti` implementation of `rtti`. Here is its full +source: + +[source,c++] +---- +struct std_rtti : rtti { + template + static type_id static_type() { + return reinterpret_cast(&typeid(T)); + } + + template + static type_id dynamic_type(const T& obj) { + return reinterpret_cast(&typeid(obj)); + } + + template + static void type_name(type_id type, Stream& stream) { + stream << reinterpret_cast(type)->name(); + } + + static std::type_index type_index(type_id type) { + return std::type_index(*reinterpret_cast(type)); + } + + template + static D dynamic_cast_ref(B&& obj) { + return dynamic_cast(obj); + } +}; +---- + +* `static_type` is used by class registration, by `virtual_ptr`{empty}'s "final" + constructs, and to format error and trace messages. `T` is not restricted to + the classes that appear as virtual parameters. This function is required. + +* `dynamic_type` is used to locate the v-table for an object. This function is + usually required. If only the `virtual_ptr` "final" constructs are used, or + if `boost_openmethod_vptr` is provided for all the classes in the policy, it + can be omitted. + +* `type_name` writes a representation of `type` to `stream`. It is used to format + error and trace messages. `Stream` is a lighweight version of `std::ostream` + with reduced functionality. It only supports insertion of `const char*`, + `std::string_view`, pointers and `std::size_t`. This function is optional; + if it is not provided, "type_id(_type_)" is used. + +* `type_index` returns an object that _uniquely_ identifies a class. Some forms + of RTTI (most notably, C++'s `typeid` operator) do not guarantee that the + type information object for a class is unique within the same program. This + function is optional; if not provided, `type` is assumed to be unique, and + used as is. + +* `dynamic_cast_ref` casts `obj` to class `D`. `B&&` is either a lvalue reference + (possibly cv-qualified) or a rvalue reference. `D` has the same reference + category (and cv-qualifier if applicable) as `B`. This function is required + only in presence of virtual inheritance. + +Consider a custom RTTI implementation: + +[source,c++] +---- +struct Animal { + Animal(unsigned type) : type(type) { + } + + virtual ~Animal() = default; + + unsigned type; + static constexpr unsigned static_type = 1; +}; + +struct Cat : Animal { + Cat() : Animal(static_type) { + } + + static constexpr unsigned static_type = 2; +}; + +// ditto for Dog +---- + +This scheme has an interesting property: its type ids are monotonically +allocated in a small, dense range. Thus, we don't need to hash them. We can use +them as indexes in the table of vptrs. + +This time we are going to replace the default policy globally. First we need to +define the custom RTTI facet. We must _not_ include +`` or any header that includes it yet. + +Here is the facet implementation: + +[source,c++] +---- +include::{examplesdir}/custom_rtti.cpp[tag=facet] +---- + +This facet is quite minimal. It does not support virtual inheritance. It would +not produce good error or trace messages, because types would be represented by +their integer ids. + +This time we create a policy from scratch. For that we use the `basic_policy` +CRTP template: + +[source,c++] +---- +include::{examplesdir}/custom_rtti.cpp[tag=policy] +---- + +Now we can include the "core" header and write the example: + +[source,c++] +---- +include::{examplesdir}/custom_rtti.cpp[tag=example] +---- + +This programs compiles even if standard RTTI is disabled. + +## Deferred RTTI + +In the previous example, the RTTI system assigns types id statically. It is more +common to allocate them using a global counter, manipulated by static +constructors. This is a problem, because `static_type` is used by class +registration. It may read the custom type ids _before_ they are have been +initialized. + +The solution is to add the `deferred_static_rtti` facet to the policy; it defers +reading the type information until `initialize` is called. + +This time let's support virtual inheritance as well. First the domain classes: + +[source,c++] +---- +include::{examplesdir}/deferred_custom_rtti.cpp[tag=classes] +// ditto for Dog +---- + +The rtti facet is the same, with one more function: + +[source,c++] +---- +struct custom_rtti : bom::policies::rtti { + // as before + +include::{examplesdir}/deferred_custom_rtti.cpp[tag=dynamic_cast_ref] +}; +---- + +Finally, the policy contains an additional facet - `deferred_static_rtti`: + +[source,c++] +---- +struct custom_policy + : bom::policies::basic_policy< + custom_policy, custom_rtti, + bom::policies::deferred_static_rtti, // <-- additional facet + bom::policies::vptr_vector> {}; +---- + +The example is the same as in the previous section. diff --git a/doc/deferred_static_rtti.adoc b/doc/deferred_static_rtti.adoc new file mode 100644 index 0000000..17ceda4 --- /dev/null +++ b/doc/deferred_static_rtti.adoc @@ -0,0 +1,25 @@ + +## deferred_static_rtti + +### Synopsis + +Defined in . + +```c++ +namespace boost::openmethod::policies { + +struct deferred_static_rtti : virtual rtti {}; + +} +``` + +### Description + +`deferred_static_rtti` is a facet that defers collection of static type ids. + +Some custom RTTI systems rely on static constructors to assign type ids. +OpenMethod itself relies on static constructors to register classes, methods and +overriders, calling the `static_type` function from the `rtti` facet in the +process. This can result in collecting the type ids _before_ they have been +initialized. Adding this facet to a policy moves the collection of type ids to +`initialize`. diff --git a/doc/docinfo.html b/doc/docinfo.html new file mode 100644 index 0000000..5e64c36 --- /dev/null +++ b/doc/docinfo.html @@ -0,0 +1,12 @@ + + + + diff --git a/doc/domain.adoc b/doc/domain.adoc new file mode 100644 index 0000000..19a4468 --- /dev/null +++ b/doc/domain.adoc @@ -0,0 +1,34 @@ + +## domain + +### Synopsis + +Defined in . + +```c++ +namespace boost::openmethod::policies { + +template +struct domain { + template static vptr_type static_vptr; + // unspecified members +}; + +} +``` + +### Description + +`domain` is a registry of classes and methods registered in a _Policy_, +and their dispatch tables. + +### Members + +#### static_vptr + +```c++ +template +static vptr_type static_vptr; +``` + +Contains the pointer to the v-table for _Class_. Set by `initialize`. diff --git a/doc/error_handler.adoc b/doc/error_handler.adoc new file mode 100644 index 0000000..4994747 --- /dev/null +++ b/doc/error_handler.adoc @@ -0,0 +1,63 @@ + +## error_handler + +### Synopsis + +Defined in . + +```c++ +namespace boost::openmethod::policies { + +struct error_handler; + +} +``` + +Defined in . + +```c++ +namespace boost::openmethod { + +struct openmethod_error {}; + +struct not_implemented_error : openmethod_error { + type_id method; + std::size_t arity; + static constexpr std::size_t max_types = 16; + type_id types[max_types]; +}; + +struct unknown_class_error : openmethod_error { + type_id type; +}; + +struct hash_search_error : openmethod_error { + std::size_t attempts; + std::size_t buckets; +}; + +struct type_mismatch_error : openmethod_error { + type_id type; +}; + +} // boost::openmethod::policies +``` + +### Description + +`error_handler` is a facet that handles errors. + +When an error is encountered, either during `initialize` or method dispatch, the +program is terminated via a call to `abort`. If this facet is present in the +policy, its `error` function is called with an error object. It can prevent +termination by throwing an exception. + +### Requirements + +Implementations of `error_handler` must provide the following functions: + +#### error + +```c++ +| static auto error(const T&) -> void; +``` diff --git a/doc/error_handling.adoc b/doc/error_handling.adoc new file mode 100644 index 0000000..9f1acf0 --- /dev/null +++ b/doc/error_handling.adoc @@ -0,0 +1,55 @@ + +## Error Handling + +When an error is encountered, the program is terminated by a call to `abort`. If +the policy contains an `error_handler` facet, it provides an `error` member +function (or overloaded functions) to be called with an object identifying the +error. The `release` and `debug` policies implement the error facet with +`vectored_error_handler`, which wraps the error object in a variant, and calls a +handler via a `std::function`. By default, it prints a description of the error +to `stderr` in the `debug` policy, and does nothing in the `release` policy. The +handler can be set with `set_error_handler`: + +[source,c++] +---- +include::{examplesdir}/vectored_error_handler.cpp[tag=example] +---- + +Output: + +[source,console] +---- +spin +not implemented +spin +---- + +We can also replace the `error_handler` facet with our own. For example: + + +[source,c++] +---- +include::{examplesdir}/throw_error_handler.cpp[tag=example] +---- + +[source,console] +---- +spin +not implemented +spin +---- + +Stock facet `throw_error_handler` does this for all the exception types: + +```c++ +namespace boost::openmethod::policies { + +struct throw_error_handler : error_handler { + template + [[noreturn]] static auto error(const Error& error) -> void { + throw error; + } +}; + +} // namespace boost::openmethod::policies +``` diff --git a/doc/error_output.adoc b/doc/error_output.adoc new file mode 100644 index 0000000..ef7935f --- /dev/null +++ b/doc/error_output.adoc @@ -0,0 +1,28 @@ + +## error_output + +### Synopsis + +Defined in . + +```c++ +namespace boost::openmethod::policies { + +struct error_output {}; + +} // boost::openmethod::policies +``` + +### Description + +`error_output` is a facet that provides a stream for writing error messages. + +### Requirements + +#### error_stream + +```c++ +static RestrictedOutputStream error_stream; +``` + +A static variable that satisfies the requirements of `RestrictedOutputStream`. diff --git a/doc/friendship.adoc b/doc/friendship.adoc new file mode 100644 index 0000000..5be869f --- /dev/null +++ b/doc/friendship.adoc @@ -0,0 +1,39 @@ + +## Friendship + +Overriders are implemented as static functions located in specializations of a +template named after the method, declared in the same scope. Macro +`BOOST_OPENMETHOD_OVERRIDERS` returns that name. The template argument for a +specialization is the signature of the overrider. For example, the overrider of +`poke` for `Cat` is: + +[source,c++] +---- +BOOST_OPENMETHOD_OVERRIDERS(poke)< + void(std::ostream& os, virtual_ptr cat)>::fn; +---- + +We can thus grant friendship to all the overriders of `poke`: + +[source,c++] +---- +include::{examplesdir}/friendship.cpp[tag=friend_all] +---- + +Be aware, though, that the overriders of _any_ method called `poke` - even with +a different signature - are granted friendship. + +We can also grant friendship to individual overriders: + +[source,c++] +---- +include::{examplesdir}/friendship.cpp[tag=friend] +---- + +// If the overriders exist in a different namespace, we must take into account that +// the overriders template is declared in the current namespace. + +// [source,c++] +// ---- +// include::{examplesdir}/friendship_across_namespaces.cpp[tag=friend] +// ---- diff --git a/doc/headers.adoc b/doc/headers.adoc new file mode 100644 index 0000000..a6bf228 --- /dev/null +++ b/doc/headers.adoc @@ -0,0 +1,37 @@ +### + +### + +### + +### + +### + +### + +### + +### + +### + +### + +### + +### + +### + +### + +### + +### + +### + +### + +### diff --git a/doc/hello_world.adoc b/doc/hello_world.adoc new file mode 100644 index 0000000..3271822 --- /dev/null +++ b/doc/hello_world.adoc @@ -0,0 +1,103 @@ + +## Hello World + +Consider the following program, intended to demonstrate the basics of virtual +functions: + +[source,c++] +---- +include::{examplesdir}/virtual_func.cpp[tag=code] +---- + +We are going to rewrite this using open-methods. + +First we remove the `poke` virtual functions from the domain classes: + +[source,c++] +---- +include::{examplesdir}/hello_world.cpp[tag=domain_classes] +---- + +Note that the Animal classes do not depend on iostreams anymore. This is a major +advantage of open-methods over virtual functions: they make it possible to +better organize dependencies. + +Let's implement `poke`. First we need to include the library's main header. It +defines a few macros, and injects a name - `virtual_ptr` - in the global +namespace. + +[source,c++] +---- +include::{examplesdir}/hello_world.cpp[tag=method] +---- + +This defines a free function called `poke`, which takes two arguments. The first +is the `ostream`. The second argument corresponds to the implicit `this` pointer +in a virtual function. It is now an explicit argument. Just like with virtual +functions, the exact function to execute is selected on the basis of the +argument's _dynamic_ type. + +Unlike virtual functions, there is no such thing as a pure open-method that +would make a class abstract. It is not possible to determine if an overrider is +available from looking at just the current translation unit. + +Let's add overriders for `Cat` and `Dog`: + +[source,c++] +---- +include::{examplesdir}/hello_world.cpp[tag=overriders] +---- + +`Bulldog::poke` calls the `poke` it overrides in its `Dog` base. The equivalent +for open-methods is `next`, a function that is available only inside the body of +an overrider. It calls the next most specific overrider, i.e. what would have +been called if the overrider did not exist. + +[source,c++] +---- +include::{examplesdir}/hello_world.cpp[tag=next] +---- + +All classes involved in open-method calls need to be registered using the +`BOOST_OPENMETHOD_CLASSES` macro: + +[source,c++] +---- +include::{examplesdir}/hello_world.cpp[tag=classes] +---- + +Classes can be registered incrementally, as long as all the direct bases of a +class are listed with it in some call(s) to `BOOST_OPENMETHOD_CLASSES`. For +example, `Bulldog` can be added in a second call, as long as `Dog` is listed as +well: + +[source,c++] +---- +// in animals.cpp +BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog); + +// in bulldog.cpp +BOOST_OPENMETHOD_CLASSES(Dog, Bulldog); +---- + +`boost::openmethod::initialize();` must be called before any open-method call. +It builds the dispatch tables. Typically this is done in `main`: + +[source,c++] +---- +include::{examplesdir}/hello_world.cpp[tag=main,indent=0] +---- + +We call `poke` like any ordinary function. We can pass it the animals by +reference, because `virtual_ptr` has a conversion constructor for that: + + +[source,c++] +---- +include::{examplesdir}/hello_world.cpp[tag=call] +---- + +NOTE: `virtual_ptr` is more like a reference than a pointer: it cannot be null, +and it cannot be re-assigned. The only reason why it is not called `virtual_ref` +is to save the name in case it becomes possible to overload the dot operator in +future versions of C++. diff --git a/doc/html/rouge-github.css b/doc/html/rouge-github.css new file mode 100644 index 0000000..b126f17 --- /dev/null +++ b/doc/html/rouge-github.css @@ -0,0 +1,199 @@ +.highlight table td { padding: 5px; } +.highlight table pre { margin: 0; } +.highlight .cm { + color: #999988; + font-style: italic; +} +.highlight .cp { + color: #999999; + font-weight: bold; +} +.highlight .c1 { + color: #999988; + font-style: italic; +} +.highlight .cs { + color: #999999; + font-weight: bold; + font-style: italic; +} +.highlight .c, .highlight .cd { + color: #999988; + font-style: italic; +} +.highlight .err { +} +.highlight .gd { + color: #000000; + background-color: #ffdddd; +} +.highlight .ge { + color: #000000; + font-style: italic; +} +.highlight .gr { + color: #aa0000; +} +.highlight .gh { + color: #999999; +} +.highlight .gi { + color: #000000; + background-color: #ddffdd; +} +.highlight .go { + color: #888888; +} +.highlight .gp { + color: #555555; +} +.highlight .gs { + font-weight: bold; +} +.highlight .gu { + color: #aaaaaa; +} +.highlight .gt { + color: #aa0000; +} +.highlight .kc { + font-weight: bold; +} +.highlight .kd { + font-weight: bold; +} +.highlight .kn { + font-weight: bold; +} +.highlight .kp { + font-weight: bold; +} +.highlight .kr { + font-weight: bold; +} +.highlight .kt { + color: #445588; + font-weight: bold; +} +.highlight .k, .highlight .kv { + font-weight: bold; +} +.highlight .mf { + color: #009999; +} +.highlight .mh { + color: #009999; +} +.highlight .il { + color: #009999; +} +.highlight .mi { + color: #009999; +} +.highlight .mo { + color: #009999; +} +.highlight .m, .highlight .mb, .highlight .mx { + color: #009999; +} +.highlight .sb { + color: #d14; +} +.highlight .sc { + color: #d14; +} +.highlight .sd { + color: #d14; +} +.highlight .s2 { + color: #d14; +} +.highlight .se { + color: #d14; +} +.highlight .sh { + color: #d14; +} +.highlight .si { + color: #d14; +} +.highlight .sx { + color: #d14; +} +.highlight .sr { + color: #009926; +} +.highlight .s1 { + color: #d14; +} +.highlight .ss { + color: #990073; +} +.highlight .s { + color: #d14; +} +.highlight .na { + color: #008080; +} +.highlight .bp { + color: #999999; +} +.highlight .nb { + color: #0086B3; +} +.highlight .nc { + color: #445588; + font-weight: bold; +} +.highlight .no { + color: #008080; +} +.highlight .nd { + color: #3c5d5d; + font-weight: bold; +} +.highlight .ni { + color: #800080; +} +.highlight .ne { + color: #990000; + font-weight: bold; +} +.highlight .nf { + color: #990000; + font-weight: bold; +} +.highlight .nl { + color: #990000; + font-weight: bold; +} +.highlight .nn { + color: #555555; +} +.highlight .nt { + color: #000080; +} +.highlight .vc { + color: #008080; +} +.highlight .vg { + color: #008080; +} +.highlight .vi { + color: #008080; +} +.highlight .nv { + color: #008080; +} +.highlight .ow { + font-weight: bold; +} +.highlight .o { + font-weight: bold; +} +.highlight .o { + font-weight: bold; +} +.highlight .w { + color: #bbbbbb; +} diff --git a/doc/html/skin.png b/doc/html/skin.png new file mode 100644 index 0000000..23540cc Binary files /dev/null and b/doc/html/skin.png differ diff --git a/doc/html/zajo-dark.css b/doc/html/zajo-dark.css new file mode 100644 index 0000000..4247e85 --- /dev/null +++ b/doc/html/zajo-dark.css @@ -0,0 +1,478 @@ +/* Asciidoctor default stylesheet | MIT License | http://asciidoctor.org */ +/* Uncomment @import statement below to use as custom stylesheet */ +/*@import "https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700";*/ + +/* Zajo's custom font import. The rest of the customizations are at the bottom of this css file, which is otherwise kept unchanged */ +@import "https://fonts.googleapis.com/css?family=Anonymous+Pro|Istok+Web|Quicksand|Poiret+One"; + +article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block} +audio,canvas,video{display:inline-block} +audio:not([controls]){display:none;height:0} +script{display:none!important} +html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%} +a{background:transparent} +a:focus{outline:thin dotted} +a:active,a:hover{outline:0} +h1{font-size:2em;margin:.67em 0} +abbr[title]{border-bottom:1px dotted} +b,strong{font-weight:bold} +dfn{font-style:italic} +hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0} +mark{background:#ff0;color:#000} +code,kbd,pre,samp{font-family:monospace;font-size:1em} +pre{white-space:pre-wrap} +q{quotes:"\201C" "\201D" "\2018" "\2019"} +small{font-size:80%} +sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline} +sup{top:-.5em} +sub{bottom:-.25em} +img{border:0} +svg:not(:root){overflow:hidden} +figure{margin:0} +fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em} +legend{border:0;padding:0} +button,input,select,textarea{font-family:inherit;font-size:100%;margin:0} +button,input{line-height:normal} +button,select{text-transform:none} +button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer} +button[disabled],html input[disabled]{cursor:default} +input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0} +button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0} +textarea{overflow:auto;vertical-align:top} +table{border-collapse:collapse;border-spacing:0} +*,*::before,*::after{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box} +html,body{font-size:100%} +body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;font-weight:400;font-style:normal;line-height:1;position:relative;cursor:auto;tab-size:4;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased} +a:hover{cursor:pointer} +img,object,embed{max-width:100%;height:auto} +object,embed{height:100%} +img{-ms-interpolation-mode:bicubic} +.left{float:left!important} +.right{float:right!important} +.text-left{text-align:left!important} +.text-right{text-align:right!important} +.text-center{text-align:center!important} +.text-justify{text-align:justify!important} +.hide{display:none} +img,object,svg{display:inline-block;vertical-align:middle} +textarea{height:auto;min-height:50px} +select{width:100%} +.center{margin-left:auto;margin-right:auto} +.stretch{width:100%} +.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em} +div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0;direction:ltr} +a{color:#2156a5;text-decoration:underline;line-height:inherit} +a:hover,a:focus{color:#1d4b8f} +a img{border:none} +p{font-family:inherit;font-weight:400;font-size:1em;line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility} +p aside{font-size:.875em;line-height:1.35;font-style:italic} +h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em} +h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0} +h1{font-size:2.125em} +h2{font-size:1.6875em} +h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em} +h4,h5{font-size:1.125em} +h6{font-size:1em} +hr{border:solid #dddddf;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em;height:0} +em,i{font-style:italic;line-height:inherit} +strong,b{font-weight:bold;line-height:inherit} +small{font-size:60%;line-height:inherit} +code{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;color:rgba(0,0,0,.9)} +ul,ol,dl{font-size:1em;line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit} +ul,ol{margin-left:1.5em} +ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0;font-size:1em} +ul.square li ul,ul.circle li ul,ul.disc li ul{list-style:inherit} +ul.square{list-style-type:square} +ul.circle{list-style-type:circle} +ul.disc{list-style-type:disc} +ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0} +dl dt{margin-bottom:.3125em;font-weight:bold} +dl dd{margin-bottom:1.25em} +abbr,acronym{text-transform:uppercase;font-size:90%;color:rgba(0,0,0,.8);border-bottom:1px dotted #ddd;cursor:help} +abbr{text-transform:none} +blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd} +blockquote cite{display:block;font-size:.9375em;color:rgba(0,0,0,.6)} +blockquote cite::before{content:"\2014 \0020"} +blockquote cite a,blockquote cite a:visited{color:rgba(0,0,0,.6)} +blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)} +@media screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2} +h1{font-size:2.75em} +h2{font-size:2.3125em} +h3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em} +h4{font-size:1.4375em}} +table{background:#fff;margin-bottom:1.25em;border:solid 1px #dedede} +table thead,table tfoot{background:#f7f8f7} +table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left} +table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)} +table tr.even,table tr.alt,table tr:nth-of-type(even){background:#f8f8f7} +table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{display:table-cell;line-height:1.6} +h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2;word-spacing:-.05em} +h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400} +.clearfix::before,.clearfix::after,.float-group::before,.float-group::after{content:" ";display:table} +.clearfix::after,.float-group::after{clear:both} +*:not(pre)>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background-color:#f7f7f8;-webkit-border-radius:4px;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed;word-wrap:break-word} +*:not(pre)>code.nobreak{word-wrap:normal} +*:not(pre)>code.nowrap{white-space:nowrap} +pre,pre>code{line-height:1.45;color:rgba(0,0,0,.9);font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;text-rendering:optimizeSpeed} +em em{font-style:normal} +strong strong{font-weight:400} +.keyseq{color:rgba(51,51,51,.8)} +kbd{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background-color:#f7f7f7;border:1px solid #ccc;-webkit-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em white inset;box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em #fff inset;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap} +.keyseq kbd:first-child{margin-left:0} +.keyseq kbd:last-child{margin-right:0} +.menuseq,.menuref{color:#000} +.menuseq b:not(.caret),.menuref{font-weight:inherit} +.menuseq{word-spacing:-.02em} +.menuseq b.caret{font-size:1.25em;line-height:.8} +.menuseq i.caret{font-weight:bold;text-align:center;width:.45em} +b.button::before,b.button::after{position:relative;top:-1px;font-weight:400} +b.button::before{content:"[";padding:0 3px 0 2px} +b.button::after{content:"]";padding:0 2px 0 3px} +p a>code:hover{color:rgba(0,0,0,.9)} +#header,#content,#footnotes,#footer{width:100%;margin-left:auto;margin-right:auto;margin-top:0;margin-bottom:0;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em} +#header::before,#header::after,#content::before,#content::after,#footnotes::before,#footnotes::after,#footer::before,#footer::after{content:" ";display:table} +#header::after,#content::after,#footnotes::after,#footer::after{clear:both} +#content{margin-top:1.25em} +#content::before{content:none} +#header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0} +#header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #dddddf} +#header>h1:only-child,body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #dddddf;padding-bottom:8px} +#header .details{border-bottom:1px solid #dddddf;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:-ms-flexbox;display:-webkit-flex;display:flex;-ms-flex-flow:row wrap;-webkit-flex-flow:row wrap;flex-flow:row wrap} +#header .details span:first-child{margin-left:-.125em} +#header .details span.email a{color:rgba(0,0,0,.85)} +#header .details br{display:none} +#header .details br+span::before{content:"\00a0\2013\00a0"} +#header .details br+span.author::before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)} +#header .details br+span#revremark::before{content:"\00a0|\00a0"} +#header #revnumber{text-transform:capitalize} +#header #revnumber::after{content:"\00a0"} +#content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #dddddf;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem} +#toc{border-bottom:1px solid #e7e7e9;padding-bottom:.5em} +#toc>ul{margin-left:.125em} +#toc ul.sectlevel0>li>a{font-style:italic} +#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0} +#toc ul{font-family:"Open Sans","DejaVu Sans",sans-serif;list-style-type:none} +#toc li{line-height:1.3334;margin-top:.3334em} +#toc a{text-decoration:none} +#toc a:active{text-decoration:underline} +#toctitle{color:#7a2518;font-size:1.2em} +@media screen and (min-width:768px){#toctitle{font-size:1.375em} +body.toc2{padding-left:15em;padding-right:0} +#toc.toc2{margin-top:0!important;background-color:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #e7e7e9;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto} +#toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em} +#toc.toc2>ul{font-size:.9em;margin-bottom:0} +#toc.toc2 ul ul{margin-left:0;padding-left:1em} +#toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em} +body.toc2.toc-right{padding-left:0;padding-right:15em} +body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #e7e7e9;left:auto;right:0}} +@media screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0} +#toc.toc2{width:20em} +#toc.toc2 #toctitle{font-size:1.375em} +#toc.toc2>ul{font-size:.95em} +#toc.toc2 ul ul{padding-left:1.25em} +body.toc2.toc-right{padding-left:0;padding-right:20em}} +#content #toc{border-style:solid;border-width:1px;border-color:#e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;-webkit-border-radius:4px;border-radius:4px} +#content #toc>:first-child{margin-top:0} +#content #toc>:last-child{margin-bottom:0} +#footer{max-width:100%;background-color:rgba(0,0,0,.8);padding:1.25em} +#footer-text{color:rgba(255,255,255,.8);line-height:1.44} +#content{margin-bottom:.625em} +.sect1{padding-bottom:.625em} +@media screen and (min-width:768px){#content{margin-bottom:1.25em} +.sect1{padding-bottom:1.25em}} +.sect1:last-child{padding-bottom:0} +.sect1+.sect1{border-top:1px solid #e7e7e9} +#content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400} +#content h1>a.anchor::before,h2>a.anchor::before,h3>a.anchor::before,#toctitle>a.anchor::before,.sidebarblock>.content>.title>a.anchor::before,h4>a.anchor::before,h5>a.anchor::before,h6>a.anchor::before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em} +#content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible} +#content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none} +#content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221} +.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em} +.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:"Noto Serif","DejaVu Serif",serif;font-size:1rem;font-style:italic} +table.tableblock.fit-content>caption.title{white-space:nowrap;width:0} +.paragraph.lead>p,#preamble>.sectionbody>[class="paragraph"]:first-of-type p{font-size:1.21875em;line-height:1.6;color:rgba(0,0,0,.85)} +table.tableblock #preamble>.sectionbody>[class="paragraph"]:first-of-type p{font-size:inherit} +.admonitionblock>table{border-collapse:separate;border:0;background:none;width:100%} +.admonitionblock>table td.icon{text-align:center;width:80px} +.admonitionblock>table td.icon img{max-width:none} +.admonitionblock>table td.icon .title{font-weight:bold;font-family:"Open Sans","DejaVu Sans",sans-serif;text-transform:uppercase} +.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #dddddf;color:rgba(0,0,0,.6)} +.admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0} +.exampleblock>.content{border-style:solid;border-width:1px;border-color:#e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;-webkit-border-radius:4px;border-radius:4px} +.exampleblock>.content>:first-child{margin-top:0} +.exampleblock>.content>:last-child{margin-bottom:0} +.sidebarblock{border-style:solid;border-width:1px;border-color:#e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;-webkit-border-radius:4px;border-radius:4px} +.sidebarblock>:first-child{margin-top:0} +.sidebarblock>:last-child{margin-bottom:0} +.sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center} +.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0} +.literalblock pre,.listingblock pre:not(.highlight),.listingblock pre[class="highlight"],.listingblock pre[class^="highlight "],.listingblock pre.CodeRay,.listingblock pre.prettyprint{background:#f7f7f8} +.sidebarblock .literalblock pre,.sidebarblock .listingblock pre:not(.highlight),.sidebarblock .listingblock pre[class="highlight"],.sidebarblock .listingblock pre[class^="highlight "],.sidebarblock .listingblock pre.CodeRay,.sidebarblock .listingblock pre.prettyprint{background:#f2f1f1} +.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{-webkit-border-radius:4px;border-radius:4px;word-wrap:break-word;overflow-x:auto;padding:1em;font-size:.8125em} +@media screen and (min-width:768px){.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{font-size:.90625em}} +@media screen and (min-width:1280px){.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{font-size:1em}} +.literalblock pre.nowrap,.literalblock pre.nowrap pre,.listingblock pre.nowrap,.listingblock pre.nowrap pre{white-space:pre;word-wrap:normal} +.literalblock.output pre{color:#f7f7f8;background-color:rgba(0,0,0,.9)} +.listingblock pre.highlightjs{padding:0} +.listingblock pre.highlightjs>code{padding:1em;-webkit-border-radius:4px;border-radius:4px} +.listingblock pre.prettyprint{border-width:0} +.listingblock>.content{position:relative} +.listingblock code[data-lang]::before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:#999} +.listingblock:hover code[data-lang]::before{display:block} +.listingblock.terminal pre .command::before{content:attr(data-prompt);padding-right:.5em;color:#999} +.listingblock.terminal pre .command:not([data-prompt])::before{content:"$"} +table.pyhltable{border-collapse:separate;border:0;margin-bottom:0;background:none} +table.pyhltable td{vertical-align:top;padding-top:0;padding-bottom:0;line-height:1.45} +table.pyhltable td.code{padding-left:.75em;padding-right:0} +pre.pygments .lineno,table.pyhltable td:not(.code){color:#999;padding-left:0;padding-right:.5em;border-right:1px solid #dddddf} +pre.pygments .lineno{display:inline-block;margin-right:.25em} +table.pyhltable .linenodiv{background:none!important;padding-right:0!important} +.quoteblock{margin:0 1em 1.25em 1.5em;display:table} +.quoteblock>.title{margin-left:-1.5em;margin-bottom:.75em} +.quoteblock blockquote,.quoteblock p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify} +.quoteblock blockquote{margin:0;padding:0;border:0} +.quoteblock blockquote::before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)} +.quoteblock blockquote>.paragraph:last-child p{margin-bottom:0} +.quoteblock .attribution{margin-top:.75em;margin-right:.5ex;text-align:right} +.verseblock{margin:0 1em 1.25em} +.verseblock pre{font-family:"Open Sans","DejaVu Sans",sans;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility} +.verseblock pre strong{font-weight:400} +.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex} +.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic} +.quoteblock .attribution br,.verseblock .attribution br{display:none} +.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.025em;color:rgba(0,0,0,.6)} +.quoteblock.abstract blockquote::before,.quoteblock.excerpt blockquote::before,.quoteblock .quoteblock blockquote::before{display:none} +.quoteblock.abstract blockquote,.quoteblock.abstract p,.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{line-height:1.6;word-spacing:0} +.quoteblock.abstract{margin:0 1em 1.25em;display:block} +.quoteblock.abstract>.title{margin:0 0 .375em;font-size:1.15em;text-align:center} +.quoteblock.excerpt,.quoteblock .quoteblock{margin:0 0 1.25em;padding:0 0 .25em 1em;border-left:.25em solid #dddddf} +.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{color:inherit;font-size:1.0625rem} +.quoteblock.excerpt .attribution,.quoteblock .quoteblock .attribution{color:inherit;text-align:left;margin-right:0} +table.tableblock{max-width:100%;border-collapse:separate} +p.tableblock:last-child{margin-bottom:0} +td.tableblock>.content{margin-bottom:-1.25em} +table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede} +table.grid-all>thead>tr>.tableblock,table.grid-all>tbody>tr>.tableblock{border-width:0 1px 1px 0} +table.grid-all>tfoot>tr>.tableblock{border-width:1px 1px 0 0} +table.grid-cols>*>tr>.tableblock{border-width:0 1px 0 0} +table.grid-rows>thead>tr>.tableblock,table.grid-rows>tbody>tr>.tableblock{border-width:0 0 1px} +table.grid-rows>tfoot>tr>.tableblock{border-width:1px 0 0} +table.grid-all>*>tr>.tableblock:last-child,table.grid-cols>*>tr>.tableblock:last-child{border-right-width:0} +table.grid-all>tbody>tr:last-child>.tableblock,table.grid-all>thead:last-child>tr>.tableblock,table.grid-rows>tbody>tr:last-child>.tableblock,table.grid-rows>thead:last-child>tr>.tableblock{border-bottom-width:0} +table.frame-all{border-width:1px} +table.frame-sides{border-width:0 1px} +table.frame-topbot,table.frame-ends{border-width:1px 0} +table.stripes-all tr,table.stripes-odd tr:nth-of-type(odd){background:#f8f8f7} +table.stripes-none tr,table.stripes-odd tr:nth-of-type(even){background:none} +th.halign-left,td.halign-left{text-align:left} +th.halign-right,td.halign-right{text-align:right} +th.halign-center,td.halign-center{text-align:center} +th.valign-top,td.valign-top{vertical-align:top} +th.valign-bottom,td.valign-bottom{vertical-align:bottom} +th.valign-middle,td.valign-middle{vertical-align:middle} +table thead th,table tfoot th{font-weight:bold} +tbody tr th{display:table-cell;line-height:1.6;background:#f7f8f7} +tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold} +p.tableblock>code:only-child{background:none;padding:0} +p.tableblock{font-size:1em} +td>div.verse{white-space:pre} +ol{margin-left:1.75em} +ul li ol{margin-left:1.5em} +dl dd{margin-left:1.125em} +dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0} +ol>li p,ul>li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em} +ul.checklist,ul.none,ol.none,ul.no-bullet,ol.no-bullet,ol.unnumbered,ul.unstyled,ol.unstyled{list-style-type:none} +ul.no-bullet,ol.no-bullet,ol.unnumbered{margin-left:.625em} +ul.unstyled,ol.unstyled{margin-left:0} +ul.checklist{margin-left:.625em} +ul.checklist li>p:first-child>.fa-square-o:first-child,ul.checklist li>p:first-child>.fa-check-square-o:first-child{width:1.25em;font-size:.8em;position:relative;bottom:.125em} +ul.checklist li>p:first-child>input[type="checkbox"]:first-child{margin-right:.25em} +ul.inline{display:-ms-flexbox;display:-webkit-box;display:flex;-ms-flex-flow:row wrap;-webkit-flex-flow:row wrap;flex-flow:row wrap;list-style:none;margin:0 0 .625em -1.25em} +ul.inline>li{margin-left:1.25em} +.unstyled dl dt{font-weight:400;font-style:normal} +ol.arabic{list-style-type:decimal} +ol.decimal{list-style-type:decimal-leading-zero} +ol.loweralpha{list-style-type:lower-alpha} +ol.upperalpha{list-style-type:upper-alpha} +ol.lowerroman{list-style-type:lower-roman} +ol.upperroman{list-style-type:upper-roman} +ol.lowergreek{list-style-type:lower-greek} +.hdlist>table,.colist>table{border:0;background:none} +.hdlist>table>tbody>tr,.colist>table>tbody>tr{background:none} +td.hdlist1,td.hdlist2{vertical-align:top;padding:0 .625em} +td.hdlist1{font-weight:bold;padding-bottom:1.25em} +.literalblock+.colist,.listingblock+.colist{margin-top:-.5em} +.colist td:not([class]):first-child{padding:.4em .75em 0;line-height:1;vertical-align:top} +.colist td:not([class]):first-child img{max-width:none} +.colist td:not([class]):last-child{padding:.25em 0} +.thumb,.th{line-height:0;display:inline-block;border:solid 4px #fff;-webkit-box-shadow:0 0 0 1px #ddd;box-shadow:0 0 0 1px #ddd} +.imageblock.left{margin:.25em .625em 1.25em 0} +.imageblock.right{margin:.25em 0 1.25em .625em} +.imageblock>.title{margin-bottom:0} +.imageblock.thumb,.imageblock.th{border-width:6px} +.imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em} +.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0} +.image.left{margin-right:.625em} +.image.right{margin-left:.625em} +a.image{text-decoration:none;display:inline-block} +a.image object{pointer-events:none} +sup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super} +sup.footnote a,sup.footnoteref a{text-decoration:none} +sup.footnote a:active,sup.footnoteref a:active{text-decoration:underline} +#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em} +#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em;border-width:1px 0 0} +#footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;margin-bottom:.2em} +#footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none;margin-left:-1.05em} +#footnotes .footnote:last-of-type{margin-bottom:0} +#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0} +.gist .file-data>table{border:0;background:#fff;width:100%;margin-bottom:0} +.gist .file-data>table td.line-data{width:99%} +div.unbreakable{page-break-inside:avoid} +.big{font-size:larger} +.small{font-size:smaller} +.underline{text-decoration:underline} +.overline{text-decoration:overline} +.line-through{text-decoration:line-through} +.aqua{color:#00bfbf} +.aqua-background{background-color:#00fafa} +.black{color:#000} +.black-background{background-color:#000} +.blue{color:#0000bf} +.blue-background{background-color:#0000fa} +.fuchsia{color:#bf00bf} +.fuchsia-background{background-color:#fa00fa} +.gray{color:#606060} +.gray-background{background-color:#7d7d7d} +.green{color:#006000} +.green-background{background-color:#007d00} +.lime{color:#00bf00} +.lime-background{background-color:#00fa00} +.maroon{color:#600000} +.maroon-background{background-color:#7d0000} +.navy{color:#000060} +.navy-background{background-color:#00007d} +.olive{color:#606000} +.olive-background{background-color:#7d7d00} +.purple{color:#600060} +.purple-background{background-color:#7d007d} +.red{color:#bf0000} +.red-background{background-color:#fa0000} +.silver{color:#909090} +.silver-background{background-color:#bcbcbc} +.teal{color:#006060} +.teal-background{background-color:#007d7d} +.white{color:#bfbfbf} +.white-background{background-color:#fafafa} +.yellow{color:#bfbf00} +.yellow-background{background-color:#fafa00} +span.icon>.fa{cursor:default} +a span.icon>.fa{cursor:inherit} +.admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default} +.admonitionblock td.icon .icon-note::before{content:"\f05a";color:#19407c} +.admonitionblock td.icon .icon-tip::before{content:"\f0eb";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111} +.admonitionblock td.icon .icon-warning::before{content:"\f071";color:#bf6900} +.admonitionblock td.icon .icon-caution::before{content:"\f06d";color:#bf3400} +.admonitionblock td.icon .icon-important::before{content:"\f06a";color:#bf0000} +.conum[data-value]{display:inline-block;color:#fff!important;background-color:rgba(0,0,0,.8);-webkit-border-radius:100px;border-radius:100px;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold} +.conum[data-value] *{color:#fff!important} +.conum[data-value]+b{display:none} +.conum[data-value]::after{content:attr(data-value)} +pre .conum[data-value]{position:relative;top:-.125em} +b.conum *{color:inherit!important} +.conum:not([data-value]):empty{display:none} +dt,th.tableblock,td.content,div.footnote{text-rendering:optimizeLegibility} +h1,h2,p,td.content,span.alt{letter-spacing:-.01em} +p strong,td.content strong,div.footnote strong{letter-spacing:-.005em} +p,blockquote,dt,td.content,span.alt{font-size:1.0625rem} +p{margin-bottom:1.25rem} +.sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em} +.exampleblock>.content{background-color:#fffef7;border-color:#e0e0dc;-webkit-box-shadow:0 1px 4px #e0e0dc;box-shadow:0 1px 4px #e0e0dc} +.print-only{display:none!important} +@page{margin:1.25cm .75cm} +@media print{*{-webkit-box-shadow:none!important;box-shadow:none!important;text-shadow:none!important} +html{font-size:80%} +a{color:inherit!important;text-decoration:underline!important} +a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important} +a[href^="http:"]:not(.bare)::after,a[href^="https:"]:not(.bare)::after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em} +abbr[title]::after{content:" (" attr(title) ")"} +pre,blockquote,tr,img,object,svg{page-break-inside:avoid} +thead{display:table-header-group} +svg{max-width:100%} +p,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3} +h2,h3,#toctitle,.sidebarblock>.content>.title{page-break-after:avoid} +#toc,.sidebarblock,.exampleblock>.content{background:none!important} +#toc{border-bottom:1px solid #dddddf!important;padding-bottom:0!important} +body.book #header{text-align:center} +body.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em} +body.book #header .details{border:0!important;display:block;padding:0!important} +body.book #header .details span:first-child{margin-left:0!important} +body.book #header .details br{display:block} +body.book #header .details br+span::before{content:none!important} +body.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important} +body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-break-before:always} +.listingblock code[data-lang]::before{display:block} +#footer{padding:0 .9375em} +.hide-on-print{display:none!important} +.print-only{display:block!important} +.hide-for-print{display:none!important} +.show-for-print{display:inherit!important}} +@media print,amzn-kf8{#header>h1:first-child{margin-top:1.25rem} +.sect1{padding:0!important} +.sect1+.sect1{border:0} +#footer{background:none} +#footer-text{color:rgba(0,0,0,.6);font-size:.9em}} +@media amzn-kf8{#header,#content,#footnotes,#footer{padding:0}} + +/* Zajo's customizations applied on top of the standard asciidoctor css above */ +h1{font-size:4em} +h2{font-size:1.74em} +h3,#toctitle,.sidebarblock>.content>.title{font-size:1.5em} +h4{font-size:1.2em} +h5{font-size:1em} +h6{font-size:1em} +#toc {text-align:left} +#toc ul code{font-size:111%} +#toc a:hover code {color:#00cc99} +a:focus{outline:0} +.colist td{color:rgba(255,255,255,.67)} +body{text-align:left; background:#202020;color:rgba(255,255,255,.67);padding:0;margin:0;font-family:"Istok Web","DejaVu Serif",serif;font-weight:400;font-style:normal;line-height:1;position:relative;cursor:auto;tab-size:4;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased} +.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#a0a0a0;font-weight:400;margin-top:0;margin-bottom:.25em} +.literalblock pre,.listingblock pre:not(.highlight),.listingblock pre[class="highlight"],.listingblock pre[class^="highlight "],.listingblock pre.CodeRay,.listingblock pre.prettyprint{background:#101010} +table{background:#202020;margin-bottom:1.25em;border:solid 1px #dedede} +table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(255,255,255,.67)} +table tr.even,table tr.alt,table tr:nth-of-type(even){background:#202020} +table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{color:rgba(255,255,255,.67)} +th{background-color:#404040} +a{color:#FFFFFF;text-decoration:underline;line-height:inherit} +a:hover{color:#00cc99} +a:focus{color:#FFFFFF} +h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Quicksand","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#00cc99;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.4em} +code{font-family:"Anonymous Pro","DejaVu Sans Mono",monospace;font-weight:400;color:black} +*:not(pre)>code{font-size:1.0em;font-style:normal!important;letter-spacing:0;padding:0 0;word-spacing:-.15em;background-color:transparent;-webkit-border-radius:0;border-radius:0;line-height:1.45;text-rendering:optimizeLegibility;word-wrap:break-word;color:white} +pre,pre>code{line-height:1.45;color:rgba(255,255,255,.67);font-family:"Anonymous Pro","DejaVu Sans Mono",monospace;font-weight:400;text-rendering:optimizeLegibility;font-size:1.05em;background-color:#101010} +a:not(pre)>code:hover {color:#00cc99} +kbd{font-family:"Anonymous Pro","DejaVu Sans Mono",monospace;display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background-color:#f7f7f7;border:1px solid #ccc;-webkit-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em white inset;box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em #fff inset;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap} +h1 code{color:#00cc99; font-size:113%} +h2 code{color:#00cc99; font-size:113%} +h3 code{color:#00cc99; font-size:113%} +h4 code{color:#00cc99; font-size:113%} +h5 code{color:#00cc99; font-size:113%} +#header>h1:first-child{font-family:"Poiret One";color:#00cc99;margin-top:2.25rem;margin-bottom:0;letter-spacing:-.07em} +#author{color:#a366ff} +#toc ul{font-family:"Quicksand","DejaVu Sans",sans-serif;list-style-type:none} +#toc a:hover{color:#00cc99} +#toc.toc2{background-color:#404040} +.admonitionblock td.icon .icon-note::before{content:"\f05a";color:#00cc99} +.admonitionblock td.icon .icon-tip::before{content:"\f0eb";color:#00cc99;text-shadow:none} +.admonitionblock td.icon .icon-warning::before{content:"\f071";color:#a366ff} +.admonitionblock td.icon .icon-caution::before{content:"\f06d";color:#a366ff} +.admonitionblock td.icon .icon-important::before{content:"\f06a";color:#a366ff} +.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #dddddf;color:rgba(255,255,255,.67)} +.conum[data-value]{display:inline-block;color:black!important;background-color:#d9d9d9;-webkit-border-radius:100px;border-radius:100px;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold} +.exampleblock>.content{background-color:#404040;border-color:#e0e0dc;-webkit-box-shadow:0 1px 4px #e0e0dc;box-shadow:0 1px 4px #e0e0dc} +.quoteblock {background-color:#404040} +.quoteblock blockquote,.quoteblock p{color:rgba(255,255,255,.67);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify;background-color:#404040} +.quoteblock blockquote::before{margin-left:-.8em;color:#00cc99} +.quoteblock blockquote{font-family:"Istok Web","DejaVu Serif"; font-size:1.0625rem; padding:0.5em} +.quoteblock .attribution{padding-top:.75ex;margin-top:0;margin-right:0;padding-right:.5ex;text-align:right;background-color:#202020} +.text-right{margin-top:-1em} diff --git a/doc/html/zajo-light.css b/doc/html/zajo-light.css new file mode 100644 index 0000000..89a5cda --- /dev/null +++ b/doc/html/zajo-light.css @@ -0,0 +1,468 @@ +/* Asciidoctor default stylesheet | MIT License | http://asciidoctor.org */ +/* Uncomment @import statement below to use as custom stylesheet */ +/*@import "https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700";*/ + +/* Zajo's custom font import. The rest of the customizations are at the bottom of this css file, which is otherwise kept unchanged */ +@import "https://fonts.googleapis.com/css?family=Anonymous+Pro|Istok+Web|Quicksand|Poiret+One"; + +article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block} +audio,canvas,video{display:inline-block} +audio:not([controls]){display:none;height:0} +script{display:none!important} +html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%} +a{background:transparent} +a:focus{outline:thin dotted} +a:active,a:hover{outline:0} +h1{font-size:2em;margin:.67em 0} +abbr[title]{border-bottom:1px dotted} +b,strong{font-weight:bold} +dfn{font-style:italic} +hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0} +mark{background:#ff0;color:#000} +code,kbd,pre,samp{font-family:monospace;font-size:1em} +pre{white-space:pre-wrap} +q{quotes:"\201C" "\201D" "\2018" "\2019"} +small{font-size:80%} +sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline} +sup{top:-.5em} +sub{bottom:-.25em} +img{border:0} +svg:not(:root){overflow:hidden} +figure{margin:0} +fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em} +legend{border:0;padding:0} +button,input,select,textarea{font-family:inherit;font-size:100%;margin:0} +button,input{line-height:normal} +button,select{text-transform:none} +button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer} +button[disabled],html input[disabled]{cursor:default} +input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0} +button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0} +textarea{overflow:auto;vertical-align:top} +table{border-collapse:collapse;border-spacing:0} +*,*::before,*::after{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box} +html,body{font-size:100%} +body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;font-weight:400;font-style:normal;line-height:1;position:relative;cursor:auto;tab-size:4;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased} +a:hover{cursor:pointer} +img,object,embed{max-width:100%;height:auto} +object,embed{height:100%} +img{-ms-interpolation-mode:bicubic} +.left{float:left!important} +.right{float:right!important} +.text-left{text-align:left!important} +.text-right{text-align:right!important} +.text-center{text-align:center!important} +.text-justify{text-align:justify!important} +.hide{display:none} +img,object,svg{display:inline-block;vertical-align:middle} +textarea{height:auto;min-height:50px} +select{width:100%} +.center{margin-left:auto;margin-right:auto} +.stretch{width:100%} +.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em} +div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0;direction:ltr} +a{color:#2156a5;text-decoration:underline;line-height:inherit} +a:hover,a:focus{color:#1d4b8f} +a img{border:none} +p{font-family:inherit;font-weight:400;font-size:1em;line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility} +p aside{font-size:.875em;line-height:1.35;font-style:italic} +h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em} +h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0} +h1{font-size:2.125em} +h2{font-size:1.6875em} +h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em} +h4,h5{font-size:1.125em} +h6{font-size:1em} +hr{border:solid #dddddf;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em;height:0} +em,i{font-style:italic;line-height:inherit} +strong,b{font-weight:bold;line-height:inherit} +small{font-size:60%;line-height:inherit} +code{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;color:rgba(0,0,0,.9)} +ul,ol,dl{font-size:1em;line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit} +ul,ol{margin-left:1.5em} +ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0;font-size:1em} +ul.square li ul,ul.circle li ul,ul.disc li ul{list-style:inherit} +ul.square{list-style-type:square} +ul.circle{list-style-type:circle} +ul.disc{list-style-type:disc} +ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0} +dl dt{margin-bottom:.3125em;font-weight:bold} +dl dd{margin-bottom:1.25em} +abbr,acronym{text-transform:uppercase;font-size:90%;color:rgba(0,0,0,.8);border-bottom:1px dotted #ddd;cursor:help} +abbr{text-transform:none} +blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd} +blockquote cite{display:block;font-size:.9375em;color:rgba(0,0,0,.6)} +blockquote cite::before{content:"\2014 \0020"} +blockquote cite a,blockquote cite a:visited{color:rgba(0,0,0,.6)} +blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)} +@media screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2} +h1{font-size:2.75em} +h2{font-size:2.3125em} +h3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em} +h4{font-size:1.4375em}} +table{background:#fff;margin-bottom:1.25em;border:solid 1px #dedede} +table thead,table tfoot{background:#f7f8f7} +table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left} +table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)} +table tr.even,table tr.alt,table tr:nth-of-type(even){background:#f8f8f7} +table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{display:table-cell;line-height:1.6} +h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2;word-spacing:-.05em} +h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400} +.clearfix::before,.clearfix::after,.float-group::before,.float-group::after{content:" ";display:table} +.clearfix::after,.float-group::after{clear:both} +*:not(pre)>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background-color:#f7f7f8;-webkit-border-radius:4px;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed;word-wrap:break-word} +*:not(pre)>code.nobreak{word-wrap:normal} +*:not(pre)>code.nowrap{white-space:nowrap} +pre,pre>code{line-height:1.45;color:rgba(0,0,0,.9);font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;text-rendering:optimizeSpeed} +em em{font-style:normal} +strong strong{font-weight:400} +.keyseq{color:rgba(51,51,51,.8)} +kbd{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background-color:#f7f7f7;border:1px solid #ccc;-webkit-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em white inset;box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em #fff inset;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap} +.keyseq kbd:first-child{margin-left:0} +.keyseq kbd:last-child{margin-right:0} +.menuseq,.menuref{color:#000} +.menuseq b:not(.caret),.menuref{font-weight:inherit} +.menuseq{word-spacing:-.02em} +.menuseq b.caret{font-size:1.25em;line-height:.8} +.menuseq i.caret{font-weight:bold;text-align:center;width:.45em} +b.button::before,b.button::after{position:relative;top:-1px;font-weight:400} +b.button::before{content:"[";padding:0 3px 0 2px} +b.button::after{content:"]";padding:0 2px 0 3px} +p a>code:hover{color:rgba(0,0,0,.9)} +#header,#content,#footnotes,#footer{width:100%;margin-left:auto;margin-right:auto;margin-top:0;margin-bottom:0;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em} +#header::before,#header::after,#content::before,#content::after,#footnotes::before,#footnotes::after,#footer::before,#footer::after{content:" ";display:table} +#header::after,#content::after,#footnotes::after,#footer::after{clear:both} +#content{margin-top:1.25em} +#content::before{content:none} +#header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0} +#header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #dddddf} +#header>h1:only-child,body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #dddddf;padding-bottom:8px} +#header .details{border-bottom:1px solid #dddddf;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:-ms-flexbox;display:-webkit-flex;display:flex;-ms-flex-flow:row wrap;-webkit-flex-flow:row wrap;flex-flow:row wrap} +#header .details span:first-child{margin-left:-.125em} +#header .details span.email a{color:rgba(0,0,0,.85)} +#header .details br{display:none} +#header .details br+span::before{content:"\00a0\2013\00a0"} +#header .details br+span.author::before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)} +#header .details br+span#revremark::before{content:"\00a0|\00a0"} +#header #revnumber{text-transform:capitalize} +#header #revnumber::after{content:"\00a0"} +#content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #dddddf;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem} +#toc{border-bottom:1px solid #e7e7e9;padding-bottom:.5em} +#toc>ul{margin-left:.125em} +#toc ul.sectlevel0>li>a{font-style:italic} +#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0} +#toc ul{font-family:"Open Sans","DejaVu Sans",sans-serif;list-style-type:none} +#toc li{line-height:1.3334;margin-top:.3334em} +#toc a{text-decoration:none} +#toc a:active{text-decoration:underline} +#toctitle{color:#7a2518;font-size:1.2em} +@media screen and (min-width:768px){#toctitle{font-size:1.375em} +body.toc2{padding-left:15em;padding-right:0} +#toc.toc2{margin-top:0!important;background-color:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #e7e7e9;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto} +#toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em} +#toc.toc2>ul{font-size:.9em;margin-bottom:0} +#toc.toc2 ul ul{margin-left:0;padding-left:1em} +#toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em} +body.toc2.toc-right{padding-left:0;padding-right:15em} +body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #e7e7e9;left:auto;right:0}} +@media screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0} +#toc.toc2{width:20em} +#toc.toc2 #toctitle{font-size:1.375em} +#toc.toc2>ul{font-size:.95em} +#toc.toc2 ul ul{padding-left:1.25em} +body.toc2.toc-right{padding-left:0;padding-right:20em}} +#content #toc{border-style:solid;border-width:1px;border-color:#e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;-webkit-border-radius:4px;border-radius:4px} +#content #toc>:first-child{margin-top:0} +#content #toc>:last-child{margin-bottom:0} +#footer{max-width:100%;background-color:rgba(0,0,0,.8);padding:1.25em} +#footer-text{color:rgba(255,255,255,.8);line-height:1.44} +#content{margin-bottom:.625em} +.sect1{padding-bottom:.625em} +@media screen and (min-width:768px){#content{margin-bottom:1.25em} +.sect1{padding-bottom:1.25em}} +.sect1:last-child{padding-bottom:0} +.sect1+.sect1{border-top:1px solid #e7e7e9} +#content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400} +#content h1>a.anchor::before,h2>a.anchor::before,h3>a.anchor::before,#toctitle>a.anchor::before,.sidebarblock>.content>.title>a.anchor::before,h4>a.anchor::before,h5>a.anchor::before,h6>a.anchor::before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em} +#content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible} +#content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none} +#content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221} +.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em} +.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:"Noto Serif","DejaVu Serif",serif;font-size:1rem;font-style:italic} +table.tableblock.fit-content>caption.title{white-space:nowrap;width:0} +.paragraph.lead>p,#preamble>.sectionbody>[class="paragraph"]:first-of-type p{font-size:1.21875em;line-height:1.6;color:rgba(0,0,0,.85)} +table.tableblock #preamble>.sectionbody>[class="paragraph"]:first-of-type p{font-size:inherit} +.admonitionblock>table{border-collapse:separate;border:0;background:none;width:100%} +.admonitionblock>table td.icon{text-align:center;width:80px} +.admonitionblock>table td.icon img{max-width:none} +.admonitionblock>table td.icon .title{font-weight:bold;font-family:"Open Sans","DejaVu Sans",sans-serif;text-transform:uppercase} +.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #dddddf;color:rgba(0,0,0,.6)} +.admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0} +.exampleblock>.content{border-style:solid;border-width:1px;border-color:#e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;-webkit-border-radius:4px;border-radius:4px} +.exampleblock>.content>:first-child{margin-top:0} +.exampleblock>.content>:last-child{margin-bottom:0} +.sidebarblock{border-style:solid;border-width:1px;border-color:#e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;-webkit-border-radius:4px;border-radius:4px} +.sidebarblock>:first-child{margin-top:0} +.sidebarblock>:last-child{margin-bottom:0} +.sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center} +.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0} +.literalblock pre,.listingblock pre:not(.highlight),.listingblock pre[class="highlight"],.listingblock pre[class^="highlight "],.listingblock pre.CodeRay,.listingblock pre.prettyprint{background:#f7f7f8} +.sidebarblock .literalblock pre,.sidebarblock .listingblock pre:not(.highlight),.sidebarblock .listingblock pre[class="highlight"],.sidebarblock .listingblock pre[class^="highlight "],.sidebarblock .listingblock pre.CodeRay,.sidebarblock .listingblock pre.prettyprint{background:#f2f1f1} +.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{-webkit-border-radius:4px;border-radius:4px;word-wrap:break-word;overflow-x:auto;padding:1em;font-size:.8125em} +@media screen and (min-width:768px){.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{font-size:.90625em}} +@media screen and (min-width:1280px){.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{font-size:1em}} +.literalblock pre.nowrap,.literalblock pre.nowrap pre,.listingblock pre.nowrap,.listingblock pre.nowrap pre{white-space:pre;word-wrap:normal} +.literalblock.output pre{color:#f7f7f8;background-color:rgba(0,0,0,.9)} +.listingblock pre.highlightjs{padding:0} +.listingblock pre.highlightjs>code{padding:1em;-webkit-border-radius:4px;border-radius:4px} +.listingblock pre.prettyprint{border-width:0} +.listingblock>.content{position:relative} +.listingblock code[data-lang]::before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:#999} +.listingblock:hover code[data-lang]::before{display:block} +.listingblock.terminal pre .command::before{content:attr(data-prompt);padding-right:.5em;color:#999} +.listingblock.terminal pre .command:not([data-prompt])::before{content:"$"} +table.pyhltable{border-collapse:separate;border:0;margin-bottom:0;background:none} +table.pyhltable td{vertical-align:top;padding-top:0;padding-bottom:0;line-height:1.45} +table.pyhltable td.code{padding-left:.75em;padding-right:0} +pre.pygments .lineno,table.pyhltable td:not(.code){color:#999;padding-left:0;padding-right:.5em;border-right:1px solid #dddddf} +pre.pygments .lineno{display:inline-block;margin-right:.25em} +table.pyhltable .linenodiv{background:none!important;padding-right:0!important} +.quoteblock{margin:0 1em 1.25em 1.5em;display:table} +.quoteblock>.title{margin-left:-1.5em;margin-bottom:.75em} +.quoteblock blockquote,.quoteblock p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify} +.quoteblock blockquote{margin:0;padding:0;border:0} +.quoteblock blockquote::before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)} +.quoteblock blockquote>.paragraph:last-child p{margin-bottom:0} +.quoteblock .attribution{margin-top:.75em;margin-right:.5ex;text-align:right} +.verseblock{margin:0 1em 1.25em} +.verseblock pre{font-family:"Open Sans","DejaVu Sans",sans;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility} +.verseblock pre strong{font-weight:400} +.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex} +.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic} +.quoteblock .attribution br,.verseblock .attribution br{display:none} +.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.025em;color:rgba(0,0,0,.6)} +.quoteblock.abstract blockquote::before,.quoteblock.excerpt blockquote::before,.quoteblock .quoteblock blockquote::before{display:none} +.quoteblock.abstract blockquote,.quoteblock.abstract p,.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{line-height:1.6;word-spacing:0} +.quoteblock.abstract{margin:0 1em 1.25em;display:block} +.quoteblock.abstract>.title{margin:0 0 .375em;font-size:1.15em;text-align:center} +.quoteblock.excerpt,.quoteblock .quoteblock{margin:0 0 1.25em;padding:0 0 .25em 1em;border-left:.25em solid #dddddf} +.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{color:inherit;font-size:1.0625rem} +.quoteblock.excerpt .attribution,.quoteblock .quoteblock .attribution{color:inherit;text-align:left;margin-right:0} +table.tableblock{max-width:100%;border-collapse:separate} +p.tableblock:last-child{margin-bottom:0} +td.tableblock>.content{margin-bottom:-1.25em} +table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede} +table.grid-all>thead>tr>.tableblock,table.grid-all>tbody>tr>.tableblock{border-width:0 1px 1px 0} +table.grid-all>tfoot>tr>.tableblock{border-width:1px 1px 0 0} +table.grid-cols>*>tr>.tableblock{border-width:0 1px 0 0} +table.grid-rows>thead>tr>.tableblock,table.grid-rows>tbody>tr>.tableblock{border-width:0 0 1px} +table.grid-rows>tfoot>tr>.tableblock{border-width:1px 0 0} +table.grid-all>*>tr>.tableblock:last-child,table.grid-cols>*>tr>.tableblock:last-child{border-right-width:0} +table.grid-all>tbody>tr:last-child>.tableblock,table.grid-all>thead:last-child>tr>.tableblock,table.grid-rows>tbody>tr:last-child>.tableblock,table.grid-rows>thead:last-child>tr>.tableblock{border-bottom-width:0} +table.frame-all{border-width:1px} +table.frame-sides{border-width:0 1px} +table.frame-topbot,table.frame-ends{border-width:1px 0} +table.stripes-all tr,table.stripes-odd tr:nth-of-type(odd){background:#f8f8f7} +table.stripes-none tr,table.stripes-odd tr:nth-of-type(even){background:none} +th.halign-left,td.halign-left{text-align:left} +th.halign-right,td.halign-right{text-align:right} +th.halign-center,td.halign-center{text-align:center} +th.valign-top,td.valign-top{vertical-align:top} +th.valign-bottom,td.valign-bottom{vertical-align:bottom} +th.valign-middle,td.valign-middle{vertical-align:middle} +table thead th,table tfoot th{font-weight:bold} +tbody tr th{display:table-cell;line-height:1.6;background:#f7f8f7} +tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold} +p.tableblock>code:only-child{background:none;padding:0} +p.tableblock{font-size:1em} +td>div.verse{white-space:pre} +ol{margin-left:1.75em} +ul li ol{margin-left:1.5em} +dl dd{margin-left:1.125em} +dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0} +ol>li p,ul>li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em} +ul.checklist,ul.none,ol.none,ul.no-bullet,ol.no-bullet,ol.unnumbered,ul.unstyled,ol.unstyled{list-style-type:none} +ul.no-bullet,ol.no-bullet,ol.unnumbered{margin-left:.625em} +ul.unstyled,ol.unstyled{margin-left:0} +ul.checklist{margin-left:.625em} +ul.checklist li>p:first-child>.fa-square-o:first-child,ul.checklist li>p:first-child>.fa-check-square-o:first-child{width:1.25em;font-size:.8em;position:relative;bottom:.125em} +ul.checklist li>p:first-child>input[type="checkbox"]:first-child{margin-right:.25em} +ul.inline{display:-ms-flexbox;display:-webkit-box;display:flex;-ms-flex-flow:row wrap;-webkit-flex-flow:row wrap;flex-flow:row wrap;list-style:none;margin:0 0 .625em -1.25em} +ul.inline>li{margin-left:1.25em} +.unstyled dl dt{font-weight:400;font-style:normal} +ol.arabic{list-style-type:decimal} +ol.decimal{list-style-type:decimal-leading-zero} +ol.loweralpha{list-style-type:lower-alpha} +ol.upperalpha{list-style-type:upper-alpha} +ol.lowerroman{list-style-type:lower-roman} +ol.upperroman{list-style-type:upper-roman} +ol.lowergreek{list-style-type:lower-greek} +.hdlist>table,.colist>table{border:0;background:none} +.hdlist>table>tbody>tr,.colist>table>tbody>tr{background:none} +td.hdlist1,td.hdlist2{vertical-align:top;padding:0 .625em} +td.hdlist1{font-weight:bold;padding-bottom:1.25em} +.literalblock+.colist,.listingblock+.colist{margin-top:-.5em} +.colist td:not([class]):first-child{padding:.4em .75em 0;line-height:1;vertical-align:top} +.colist td:not([class]):first-child img{max-width:none} +.colist td:not([class]):last-child{padding:.25em 0} +.thumb,.th{line-height:0;display:inline-block;border:solid 4px #fff;-webkit-box-shadow:0 0 0 1px #ddd;box-shadow:0 0 0 1px #ddd} +.imageblock.left{margin:.25em .625em 1.25em 0} +.imageblock.right{margin:.25em 0 1.25em .625em} +.imageblock>.title{margin-bottom:0} +.imageblock.thumb,.imageblock.th{border-width:6px} +.imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em} +.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0} +.image.left{margin-right:.625em} +.image.right{margin-left:.625em} +a.image{text-decoration:none;display:inline-block} +a.image object{pointer-events:none} +sup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super} +sup.footnote a,sup.footnoteref a{text-decoration:none} +sup.footnote a:active,sup.footnoteref a:active{text-decoration:underline} +#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em} +#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em;border-width:1px 0 0} +#footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;margin-bottom:.2em} +#footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none;margin-left:-1.05em} +#footnotes .footnote:last-of-type{margin-bottom:0} +#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0} +.gist .file-data>table{border:0;background:#fff;width:100%;margin-bottom:0} +.gist .file-data>table td.line-data{width:99%} +div.unbreakable{page-break-inside:avoid} +.big{font-size:larger} +.small{font-size:smaller} +.underline{text-decoration:underline} +.overline{text-decoration:overline} +.line-through{text-decoration:line-through} +.aqua{color:#00bfbf} +.aqua-background{background-color:#00fafa} +.black{color:#000} +.black-background{background-color:#000} +.blue{color:#0000bf} +.blue-background{background-color:#0000fa} +.fuchsia{color:#bf00bf} +.fuchsia-background{background-color:#fa00fa} +.gray{color:#606060} +.gray-background{background-color:#7d7d7d} +.green{color:#006000} +.green-background{background-color:#007d00} +.lime{color:#00bf00} +.lime-background{background-color:#00fa00} +.maroon{color:#600000} +.maroon-background{background-color:#7d0000} +.navy{color:#000060} +.navy-background{background-color:#00007d} +.olive{color:#606000} +.olive-background{background-color:#7d7d00} +.purple{color:#600060} +.purple-background{background-color:#7d007d} +.red{color:#bf0000} +.red-background{background-color:#fa0000} +.silver{color:#909090} +.silver-background{background-color:#bcbcbc} +.teal{color:#006060} +.teal-background{background-color:#007d7d} +.white{color:#bfbfbf} +.white-background{background-color:#fafafa} +.yellow{color:#bfbf00} +.yellow-background{background-color:#fafa00} +span.icon>.fa{cursor:default} +a span.icon>.fa{cursor:inherit} +.admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default} +.admonitionblock td.icon .icon-note::before{content:"\f05a";color:#19407c} +.admonitionblock td.icon .icon-tip::before{content:"\f0eb";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111} +.admonitionblock td.icon .icon-warning::before{content:"\f071";color:#bf6900} +.admonitionblock td.icon .icon-caution::before{content:"\f06d";color:#bf3400} +.admonitionblock td.icon .icon-important::before{content:"\f06a";color:#bf0000} +.conum[data-value]{display:inline-block;color:#fff!important;background-color:rgba(0,0,0,.8);-webkit-border-radius:100px;border-radius:100px;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold} +.conum[data-value] *{color:#fff!important} +.conum[data-value]+b{display:none} +.conum[data-value]::after{content:attr(data-value)} +pre .conum[data-value]{position:relative;top:-.125em} +b.conum *{color:inherit!important} +.conum:not([data-value]):empty{display:none} +dt,th.tableblock,td.content,div.footnote{text-rendering:optimizeLegibility} +h1,h2,p,td.content,span.alt{letter-spacing:-.01em} +p strong,td.content strong,div.footnote strong{letter-spacing:-.005em} +p,blockquote,dt,td.content,span.alt{font-size:1.0625rem} +p{margin-bottom:1.25rem} +.sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em} +.exampleblock>.content{background-color:#fffef7;border-color:#e0e0dc;-webkit-box-shadow:0 1px 4px #e0e0dc;box-shadow:0 1px 4px #e0e0dc} +.print-only{display:none!important} +@page{margin:1.25cm .75cm} +@media print{*{-webkit-box-shadow:none!important;box-shadow:none!important;text-shadow:none!important} +html{font-size:80%} +a{color:inherit!important;text-decoration:underline!important} +a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important} +a[href^="http:"]:not(.bare)::after,a[href^="https:"]:not(.bare)::after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em} +abbr[title]::after{content:" (" attr(title) ")"} +pre,blockquote,tr,img,object,svg{page-break-inside:avoid} +thead{display:table-header-group} +svg{max-width:100%} +p,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3} +h2,h3,#toctitle,.sidebarblock>.content>.title{page-break-after:avoid} +#toc,.sidebarblock,.exampleblock>.content{background:none!important} +#toc{border-bottom:1px solid #dddddf!important;padding-bottom:0!important} +body.book #header{text-align:center} +body.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em} +body.book #header .details{border:0!important;display:block;padding:0!important} +body.book #header .details span:first-child{margin-left:0!important} +body.book #header .details br{display:block} +body.book #header .details br+span::before{content:none!important} +body.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important} +body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-break-before:always} +.listingblock code[data-lang]::before{display:block} +#footer{padding:0 .9375em} +.hide-on-print{display:none!important} +.print-only{display:block!important} +.hide-for-print{display:none!important} +.show-for-print{display:inherit!important}} +@media print,amzn-kf8{#header>h1:first-child{margin-top:1.25rem} +.sect1{padding:0!important} +.sect1+.sect1{border:0} +#footer{background:none} +#footer-text{color:rgba(0,0,0,.6);font-size:.9em}} +@media amzn-kf8{#header,#content,#footnotes,#footer{padding:0}} + +/* Zajo's customizations applied on top of the standard asciidoctor css above */ +h1{font-size:4em} +h2{font-size:1.74em} +h3,#toctitle,.sidebarblock>.content>.title{font-size:1.5em} +h4{font-size:1.2em} +h5{font-size:1em} +h6{font-size:1em} +#toc {text-align:left} +#toc ul code{font-size:111%} +#toc a:hover code {color:#4101a7} +a:focus{outline:0} +.colist td{color:rgba(0,0,0,.67)} +body{text-align:left; background:#fff;color:rgba(0,0,0,.67);padding:0;margin:0;font-family:"Istok Web","DejaVu Serif",serif;font-weight:400;font-style:normal;line-height:1;position:relative;cursor:auto;tab-size:4;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased} +.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#a0a0a0;font-weight:400;margin-top:0;margin-bottom:.25em} +a{color:#000000;text-decoration:underline;line-height:inherit} +a:hover{color:#4101a7} +a:focus{color:#000000} +h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Quicksand","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#4101a7;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.4em} +code{font-family:"Anonymous Pro","DejaVu Sans Mono",monospace;font-weight:400;color:black} +*:not(pre)>code{font-size:1.0em;font-style:normal!important;letter-spacing:0;padding:0 0;word-spacing:-.15em;background-color:transparent;-webkit-border-radius:0;border-radius:0;line-height:1.45;text-rendering:optimizeLegibility;word-wrap:break-word} +pre,pre>code{line-height:1.45;color:rgba(0,0,0,.9);font-family:"Anonymous Pro","DejaVu Sans Mono",monospace;font-weight:400;text-rendering:optimizeLegibility;font-size:1.05em;background-color:#f7f8f7} +a:not(pre)>code:hover {color:#4101a7} +kbd{font-family:"Anonymous Pro","DejaVu Sans Mono",monospace;display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background-color:#f7f7f7;border:1px solid #ccc;-webkit-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em white inset;box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em #fff inset;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap} +h1 code{color:#4101a7; font-size:113%} +h2 code{color:#4101a7; font-size:113%} +h3 code{color:#4101a7; font-size:113%} +h4 code{color:#4101a7; font-size:113%} +h5 code{color:#4101a7; font-size:113%} +#header>h1:first-child{font-family:"Poiret One";color:#ff5100;margin-top:2.25rem;margin-bottom:0;letter-spacing:-.07em} +#author{color: #4101a7;} +#toc ul{font-family:"Quicksand","DejaVu Sans",sans-serif;list-style-type:none} +#toc a:hover{color:#4101a7} +#toc.toc2{background-color:#f7f8f7} +.admonitionblock td.icon .icon-note::before{content:"\f05a";color:#606060} +.admonitionblock td.icon .icon-tip::before{content:"\f0eb";color:#606060;text-shadow:none} +.admonitionblock td.icon .icon-warning::before{content:"\f071";color:#ff5100} +.admonitionblock td.icon .icon-caution::before{content:"\f06d";color:#ff5100} +.admonitionblock td.icon .icon-important::before{content:"\f06a";color:#ff5100} +.conum[data-value]{display:inline-block;color:#fff!important;background-color:#606060;-webkit-border-radius:100px;border-radius:100px;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold} +.exampleblock>.content{background-color:#ffffff;border-color:#e0e0dc;-webkit-box-shadow:0 1px 4px #e0e0dc;box-shadow:0 1px 4px #e0e0dc} +.quoteblock blockquote::before{margin-left:-.8em;color:#4101a7} +.quoteblock blockquote{font-family:"Istok Web","DejaVu Serif"; font-size:1.0625rem; padding:0.5em} +.text-right{margin-top:-1em} diff --git a/doc/introduction.adoc b/doc/introduction.adoc new file mode 100644 index 0000000..c99ac7c --- /dev/null +++ b/doc/introduction.adoc @@ -0,0 +1,61 @@ + +# Introduction + +Open-methods are similar to virtual functions, but they are not required to be +members of a class. By being both free and virtual, they provide a solution to +the Expression Problem: + +> Given a set of types, and a set of operations on these types, is it possible +to add new operations on the existing types, and new types to the existing +operations, without modifying existing code? + +As a bonus, open-methods can take more than one argument into account when +selecting the appropriate function to call - aka multiple dispatch. For that +reason, open-methods are often called multi-methods, but that term is +misleading, as it suggests that the feature is useful only when multiple +dispatch is needed. In reality, +https://openaccess.wgtn.ac.nz/articles/thesis/Multiple_Dispatch_in_Practice/16959112/1[it +has been observed] that, in large systems written in languages that support +multi-methods, most methods use single-dispatch. The real benefit is in the +solution to the Expression Problem. + +Open-methods were introduced by the Common Lisp Object System, and they are +native to many languages: Clojure, Julia, Dylan, TADS, Cecil, Diesel, Nice, etc. +Bjarne Stroustrup wanted open-methods in C++ almost from the beginning. In D&E +he writes: + +> I repeatedly considered a mechanism for a virtual function call based on more +than one object, often called multi-methods. I rejected multi-methods with +regret because I liked the idea, but couldn’t find an acceptable form under +which to accept it. [...] Multi-methods is one of the interesting what-ifs of +C++. Could I have designed and implemented them well enough at the time? Would +their applications have been important enough to warrant the effort? What other +work might have been left undone to provide the time to design and implement +multi-methods? Since about 1985, I have always felt some twinge of regret for +not providing multi-methods (Stroustrup, 1994, The Design and Evolution of +C{plus}{plus}, 13.8). + +Circa 2007, he and his PhD students Peter Pirkelbauer and Yuriy Solodkyy wrote a +series of papers and a prototype implementation based on the EDG compiler. +Unfortunately, open-methods never made it into the standard. Stroustrup bemoans, +in a more recent paper: + +> In retrospect, I don’t think that the object-oriented notation (e.g., x.f(y)) +should ever have been introduced. The traditional mathematical notation f(x,y) +is sufficient. As a side benefit, the mathematical notation would naturally have +given us multi-methods, thereby saving us from the visitor pattern workaround +(Stroustrup, 2020, Thriving in a Crowded and ChangingWorld: C++ 2006–2020). + +This library implements the features described in the +http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2216.pdf[N2216 paper], +with some +extensions: + +* a mechanism for calling the next most specialized overrider + +* support for smart pointers + +* customization points for RTTI, error handling, tracing, smart pointers... + +Multiple and virtual inheritance are supported, with the exception of repeated +inheritance. diff --git a/doc/map_vptr.adoc b/doc/map_vptr.adoc new file mode 100644 index 0000000..224a497 --- /dev/null +++ b/doc/map_vptr.adoc @@ -0,0 +1,62 @@ + +## vptr_map + +### Synopsis + +```c++ +namespace boost::openmethod::policies { + +### Synopsis + +template< + class Policy, bool IndirectVptr, + class Map = std::unordered_map< + type_id, + std::conditional_t>> +class vptr_map : extern_vptr { + static Map vptrs; + + template + static auto register_vptrs(ForwardIterator first, ForwardIterator last) -> void; + + template + static auto dynamic_vptr(const Class& arg) -> const vptr_type&; +}; + +} +``` + +### Description + +`vptr_map` is an implementation of `external_vptr that stores the pointers to +the v-tables in a map. + +`Policy` is the policy containing the facet. + +`Map` is an `AssociativeContainer`. The `mapped_type` is a pointer to a +`vptr_type` if `UseIndirectVptrs` is `void`, or a pointer to a `vptr_type` if +`UseIndirectVptrs` is `indirect_vptr`. + +### Members + +#### register_vptrs + +```c++ +template +auto register_vptrs(ForwardIterator first, ForwardIterator last) -> void; +``` + +Stores the pointers to v-tables in a _Map_. + +#### dynamic_vptr + +```c++ +template +auto dynamic_vptr(const Class& object) -> const vptr_type&; +``` + +Returns a pointer to the v-table for `object` (by reference). + +If _Policy_ contains the `runtime_checks` facet, checks if _Class_ is +registered. If it is not, and _Policy_ contains a `error_handler` facet, calls +its `error` function; then calls `abort`. diff --git a/doc/method.adoc b/doc/method.adoc new file mode 100644 index 0000000..6f36da6 --- /dev/null +++ b/doc/method.adoc @@ -0,0 +1,152 @@ + + +## method + +### Synopsis + +```c++ +namespace boost::openmethod { + +template< + typename Method, typename ReturnType, + class Policy = BOOST_OPENMETHOD_DEFAULT_POLICY> +class method; + +template +class method { + public: + using function_type = ReturnType (*)(CallParameters...); + + auto operator()(CallParameters... args) const -> ReturnType; + + static method fn; + + template + struct override; + + template + static function_type next; + + private: + method(); + method(const method&) = delete; + method(method&&) = delete; + ~method(); +}; + +} +``` + +### Description + +`method` implements an open-method that takes a parameter list - `Parameters` - +and returns a `ReturnType`. `Name` can be any type. Its purpose is to make it +possible to have multiple methods with the same signature. Typically, `Name` is +a class whose name reflects the method's purpose. + +`Parameters` must contain at least one virtual parameter, i.e. a parameter that +has a type in the form `virtual_ptr` or `virtual_`. The +dynamic types of the virtual arguments (the arguments corresponding to virtual +parameters in the method's signature) are taken into account to select the +overrider to call. + +A `method` is attached to a `Policy`, which influences several parts of the +dispatch mechanism - for example, how to obtain a v-table pointer for an object, +how to report errors, whether to perform sanity checks, etc. + +### Members + +#### constructor + +```c++ +method(); +``` + +Add the method to the list of methods registered in `Policy`. + +The constructor is private. The only instance is the static member variable +`fn`. + +#### destructor + +```c++ +~method(); +``` + +Remove the method from the list of methods registered in `Policy`. + +#### operator() + +```c++ +auto operator()(CallParameters... args) const -> ReturnType; +``` + +Call the method with the arguments `args`. + +`CallParameters` are the `Parameters` without the `virtual_` decorators. Note +that `virtual_ptr`{empty}s are preserved. + +The overrider is selected in a process similar to overloaded function +resolution, with extra rules to handle ambiguities. It proceeds as follows: + +1. Form the set of all applicable overriders. An overrider is applicable if it + can be called with the arguments passed to the method. + +2. If the set is empty, call the error handler (if present in the policy), then + terminate the program with `abort` + +3. Remove the overriders that are dominated by other overriders in the set. + Overrider A dominates overrider B if any of its virtual formal parameters is + more specialized than B's, and if none of B's virtual parameters is more + specialized than A's. + +4. If the resulting set contains only one overrider, call it. + +5. If the return type is a registered polymorphic type, remove all the + overriders that return a less specific type than the others. + +6. If the resulting set contains only one overrider, call it. + +7. Otherwise, call one of the remaining overriders. Which overrider is selected + is not specified, but it is the same across calls with the same arguments + types. + +For each virtual argument `arg`, the dispatch mechanism calls +`virtual_traits::peek(arg)` and deduces the v-table pointer from the `result`, +using the first of the following methods that applies: + +1. If `result` is a `virtual_ptr`, get the pointer to the v-table from it. + +2. If a function named `boost_openmethod_vptr` that takes `result` and returns a + `vptr_type` exists, call it. + +3. Call `Policy::dynamic_vptr(result)`. + +#### fn + +```c++ +static method fn; +``` + +The `method`{empty}'s unique instance. The method is called via the call +operator on `fn`: `method::fn(args...)`. + +#### override + +```c++ +template +struct override; +``` + +Add _Functions_ to the overriders of `method`. + +#### next + +```c++ +template +static function_type next; +``` + +Pointer to the next most specialized overrider after _Overrider_, i.e. the +overrider that would be called for the same tuple of virtual arguments if +_Overrider_ was not present. Set to `nullptr` if no such overrider exists. diff --git a/doc/method_override.adoc b/doc/method_override.adoc new file mode 100644 index 0000000..2acaa54 --- /dev/null +++ b/doc/method_override.adoc @@ -0,0 +1,64 @@ + +[#method_override] +## method::override + +### Synopsis + +```c++ +namespace boost::openmethod { + +template +template +struct method::override { + override(); + ~override(); +}; + +} +``` + +Usage: +```c++ +method::override some_unique_name; + // at file scope +``` + +### Description + +`override`, instantiated as a static object, add one or more overriders to an +open-method. + +_Functions_ must fulfill the following requirements: + +* Have the same number of formal parameters as the method. + +* Each parameter in the same position as a `virtual_ptr` in the method's +parameter list must be a `virtual_ptr`, where _U_ is covariant with _T_. The +_Policy_ of the `virtual_ptr`{empty}s must be the same as the method's _Policy_. + +* Each formal parameter in the same position as a `virtual_` parameter must have +a type that is covariant with the type of the method's parameter. + +* All other formal parameters must have the same type as the method's + corresponding parameters. + +* The return type of the overrider must be the same as the method's return type + or, if it is a polymorphic type, covariant with the method's return type. + +### Members + +#### constructor + +```c++ +override::override(); +``` + +Add _Functions_ to the overriders of `method`. + +#### Destructor + +```c++ +override::~method(); +``` + +Remove _Functions_ from the overriders of `method`. diff --git a/doc/minimal_rtti.adoc b/doc/minimal_rtti.adoc new file mode 100644 index 0000000..d0de41c --- /dev/null +++ b/doc/minimal_rtti.adoc @@ -0,0 +1,35 @@ + +## minimal_rtti + +### Synopsis + +```c++ +struct minimal_rtti : virtual rtti { + template + static auto static_type() -> type_id; +}; +``` + +### Description + +`minimal_rtti` is an implementation of the `rtti` facet that only uses static +type information. + +`minimal_rtti` provides the only function strictly required for the `rtti` +facet. + +This facet can be used in programs that call methods solely via +`virtual_ptr`{empty}s created with the "final" constructs. Virtual inheritance +is not supported. Classes are not required to be polymorphic. + +### Members + + +#### static_type + +```c++ +template +static auto static_type() -> type_id; +``` + +Returns the address of a local static `char` variable, cast to `type_id`. diff --git a/doc/multiple_dispatch.adoc b/doc/multiple_dispatch.adoc new file mode 100644 index 0000000..f9e434d --- /dev/null +++ b/doc/multiple_dispatch.adoc @@ -0,0 +1,20 @@ + +## Multiple Dispatch + +A method can have more than one `virtual_ptr` parameter. For example: + +[source,c++] +---- +include::{examplesdir}/hello_world.cpp[tag=multi] +---- + +[source,c++] +---- +include::{examplesdir}/hello_world.cpp[tag=multi_call,indent=0] +---- + +The appropriate overrider is selected using a process similar to overload +resolution, with fallback options. If one overrider is more specialized than all +the others, call it. Otherwise, the return type is used as a tie-breaker, _if_ +it is covariant with the return type of the base method. If there is still no +unique best overrider, one of the best overriders is chosen arbitrarily. diff --git a/doc/multiple_inheritance.adoc b/doc/multiple_inheritance.adoc new file mode 100644 index 0000000..dfb1652 --- /dev/null +++ b/doc/multiple_inheritance.adoc @@ -0,0 +1,7 @@ + +## Multiple Inheritance + +Multiple inheritance is supported, with the exception of repeated inheritance. + +Virtual inheritance is supported, but it incurs calls to `dynamic_cast` to cast +the method's arguments to the types required by the overrider. diff --git a/doc/openmethod-theme.yml b/doc/openmethod-theme.yml new file mode 100644 index 0000000..6128b38 --- /dev/null +++ b/doc/openmethod-theme.yml @@ -0,0 +1,28 @@ +extends: default +base: + font: + color: #404040 +literal: + font: + family: Courier + color: #000000 +admonition: + icon: + note: + stroke-color: #000000 + tip: + stroke-color: #000000 + warning: + stroke-color: #FF5100 + important: + stroke-color: #FF5100 + caution: + stroke-color: #FF5100 +conum: + font: + glyphs: circled + color: #000000 +link: + text-decoration: underline + text-decoration-width: 0.5 + font-color: #000000 diff --git a/doc/openmethod.adoc b/doc/openmethod.adoc new file mode 100644 index 0000000..2133b33 --- /dev/null +++ b/doc/openmethod.adoc @@ -0,0 +1,19 @@ + +# Boost.OpenMethod +Jean-Louis Leroy +:toc: left +:toclevels: 3 +:idprefix: +:listing-caption: Code Example +:table-caption: Illustration +:docinfo: private-footer +:source-highlighter: rouge +:source-language: c++ + +:leveloffset: +1 + +include::introduction.adoc[] +include::tutorial.adoc[] +include::reference.adoc[] + +:leveloffset: -1 diff --git a/doc/performance.adoc b/doc/performance.adoc new file mode 100644 index 0000000..62689fa --- /dev/null +++ b/doc/performance.adoc @@ -0,0 +1,102 @@ + +## Performance + +Open-methods are almost as fast as ordinary virtual member functions when +compiled with optimization. + +clang compiles the following code: + +[source,c++] +---- +include::{examplesdir}/hello_world.cpp[tag=call_poke_via_ref] +---- + +...to this on the x64 architecture (variable names have been shortened for +readability): + +[source,asm] +---- +mov rax, qword ptr [rsi] +mov rdx, qword ptr [rip + hash_mult] +imul rdx, qword ptr [rax - 8] +movzx ecx, byte ptr [rip + hash_shift] +shr rdx, cl +mov rax, qword ptr [rip + vptrs] +mov rax, qword ptr [rax + 8*rdx] +mov rcx, qword ptr [rip + poke::slots_strides] +mov rax, qword ptr [rax + 8*rcx] +jmp rax +---- + +llvm-mca estimates a throughput of 4 cycles per dispatch. Comparatively, calling +a native virtual functions takes one cycle. However, the difference is amortized +by the time spent passing the arguments and returning from the function; plus, +of course, executing the body of the function. + +Micro benchmarks suggest that dispatching an open-methods with a single virtual +argument is between 30% and 50% slower than calling the equivalent virtual +function, with an empty body and no other arguments. + +However, `call_poke` does two things: it constructs a `virtual_ptr` from +an `Animal&`; and then it calls the method. The construction of the +`virtual_ptr` is the costly part, as it involves a hash table lookup. Once that +price has been paid, the `virtual_ptr` can be used multiple times. It is passed +to the overrider, which can make further method calls through it. It can be +stored in variables in place of plain pointers. + +Let's look at another example: an AST for an arithmetic calculator: + +[source,c++] +---- +include::{examplesdir}/ast.cpp[tag=ast] +---- + +The `Negate` overrider compiles to: + +[source,asm] +---- +mov rdi, qword ptr [rsi + 8] +mov rsi, qword ptr [rsi + 16] + +mov rax, qword ptr [rip + value::slots_strides] +call qword ptr [rdi + 8*rax] + +neg eax +pop rcx +---- + +The first two instructions read the `virtual_ptr` from `this` - placing its +content in registers `rdi` and `rsi`. + +The next two instructions are the method call proper. According to llvm-mca, +they take one cycle - the same as a native virtual function call. + +When we create the `Plus` and `Negate` nodes, we call the conversion +constructors of `virtual_ptr`, which occur the cost of hash table lookups. +However, in this example, we know the exact types of the objects. In that case, +we can use `final_virtual_ptr` to construct the `virtual_ptr` using a single +instruction. For example: + +[source,c++] +---- +include::{examplesdir}/ast.cpp[tag=final,indent=0] +---- + +...compiles to: + +```asm +;; construct Literal +lea rax, [rip + vtable for Literal+16] +mov qword ptr [rsp], rax +mov dword ptr [rsp+8], 1 + +;; construct Negate +mov rax, qword ptr [rip+static_vptr] ; address of openmethod v-table +lea rcx, [rip+vtable for Negate+16] ; address of native v-table +mov qword ptr [rsp+16], rcx ; set native v-table +mov qword ptr [rsp+24], rax ; set openmethod v-table +mov rax, rsp ; address of 'one' +mov qword ptr [rsp+32], rax ; set vptr object pointer to 'one' +``` + +`final_virtual_ptr` does not require its argument to have a polymorphic type. diff --git a/doc/policies.adoc b/doc/policies.adoc new file mode 100644 index 0000000..e09bd46 --- /dev/null +++ b/doc/policies.adoc @@ -0,0 +1,91 @@ + +## Policies and Facets + +Methods and classes are scoped in a policy. A method can only reference classes +registered in the same policy. If a class is used as a virtual parameter in +methods using different policies, it must be registered with each of them. + +Class templates `use_classes`, `method`, `virtual_ptr`, and macros +`BOOST_OPENMETHOD` and `BOOST_OPENMETHOD_CLASSES`, accept an additional +argument, a policy class, which defaults to `policies::debug` in debug builds, +and `policies::release` in release builds. + +A policy has a collection of _facets_. Facets control how type information is +obtained, how vptrs are fetched, how errors are handled and printed, etc. Some +are used in `initialize` and method dispatch; some are used by other facets in +the same policy as part of their implementation. See the reference for a list of +facets and stock implementations. Policies and facets are placed in the +`boost::openmethod::policies` namespace. Two policies are provided by the +library: `release` and `debug`. + +`release` contains the following facets: + +[cols="1,1,1"] +|=== +|facet |implementation |role + +| rtti +| std_rtti +| provides type information for classes and objects + +| vptr +| vptr_vector +| stores vptrs in a global vector + +| type_hash +| fast_perfect_hash +| hash type id to an index in a vector + +| error_handler +| vectored_error_handler +| calls a handler via a `std::function` + +|=== + +`policies::debug` contains the same facets as `release`, plus a few more: + +[cols="1,1,1"] +|=== +|facet |implementation |role + +| runtime_checks +| (itself) +| enables runtime checks + +| error_output +| basic_error_output +| prints error descriptions to `stderr` + +| trace_output +| basic_trace_output +| enables `initialize` to print information about dispatch table construction to `stderr` + +|=== + +Policies, and some facets, have static variables. When it is the case, they are +implemented as CRTP classes. + +Policies can be created from scratch, using the `basic_policy` template, or by +adding or removing facets from existing policies. For example, `policies::debug` +is a tweak of `policies::release`: + +[source,c++] +---- +namespace boost::openmethod::policies { + +struct debug : release::add< + runtime_checks, basic_error_output, + basic_trace_output> {}; + +} +---- + + +`boost::openmethod::default_policy` is an alias to `release` or `debug`, +depending on the value of preprocessor symbols `NDEBUG`. The default policy can +be overriden by defining the macroprocessor symbol +`BOOST_OPENMETHOD_DEFAULT_POLICY` _before_ including +``. The value of the symbol is used as a default +template parameter for `use_classes`, `method`, `virtual_ptr`, and others. Once +the `core` header has been included, changing `BOOST_OPENMETHOD_DEFAULT_POLICY` +has no effect. See below for examples. diff --git a/doc/reference.adoc b/doc/reference.adoc new file mode 100644 index 0000000..6905b80 --- /dev/null +++ b/doc/reference.adoc @@ -0,0 +1,45 @@ + +# Reference +:toc: +:toc-title: +:idprefix: ref_ + +// include::headers.adoc[] + +include::BOOST_OPENMETHOD.adoc[] +include::BOOST_OPENMETHOD_OVERRIDE.adoc[] +include::BOOST_OPENMETHOD_INLINE_OVERRIDE.adoc[] +include::BOOST_OPENMETHOD_REGISTER.adoc[] +include::BOOST_OPENMETHOD_CLASSES.adoc[] +include::BOOST_OPENMETHOD_DEFAULT_POLICY.adoc[] +include::BOOST_OPENMETHOD_NAME.adoc[] +include::BOOST_OPENMETHOD_OVERRIDERS.adoc[] + +include::typedefs.adoc[] +include::method.adoc[] +include::method_override.adoc[] +include::virtual_ptr.adoc[] +include::virtual_traits.adoc[] +include::use_classes.adoc[] +include::virtual_.adoc[] +include::with_vptr.adoc[] + +include::abstract_policy.adoc[] +include::domain.adoc[] +include::basic_policy.adoc[] +include::rtti.adoc[] +include::std_rtti.adoc[] +include::deferred_static_rtti.adoc[] +include::minimal_rtti.adoc[] +include::vptr.adoc[] +include::vector_vptr.adoc[] +include::map_vptr.adoc[] +include::type_hash.adoc[] +include::error_handler.adoc[] +include::vectored_error_handler.adoc[] +include::throw_error_handler.adoc[] +include::error_output.adoc[] +include::basic_error_output.adoc[] +include::trace_output.adoc[] +include::basic_trace_output.adoc[] +include::restricted_output_stream.adoc[] diff --git a/doc/restricted_output_stream.adoc b/doc/restricted_output_stream.adoc new file mode 100644 index 0000000..57b2b7e --- /dev/null +++ b/doc/restricted_output_stream.adoc @@ -0,0 +1,35 @@ + +## RestrictedOutputStream + +### Description + +RestrictedOutputStream is a concept describing a `std::ostream`-like class with +a reduced set of operations. + +While convenient, `std::ostream` and its implementations constitute a sizeable +piece of code, which may make it unsuitable for certain applications. OpenMethod +uses a small subset of the operations supported by `std::ostream`. By default, +the library uses a lightweight implementation based on the C stream functions. + +Implementations of `RestrictedOutputStream` provide the following functions: + +[cols="a,a", options="header"] + +|=== + +| Name +| Description + +| RestrictedOutputStream& operator<<(RestrictedOutputStream& os, const char* str) +| Write a null-terminated string `str` to `os` + +| RestrictedOutputStream& operator<<(RestrictedOutputStream& os, const std::string_view& view) +| Write a view to `os + +| RestrictedOutputStream& operator<<(RestrictedOutputStream& os, const void* value) +| Write a representation of a pointer to `os` + +| RestrictedOutputStream& operator<<(RestrictedOutputStream& os, std::size_t value) +| Write an unsigned integer to `os` + +|=== diff --git a/doc/rouge-github.css b/doc/rouge-github.css new file mode 100644 index 0000000..b126f17 --- /dev/null +++ b/doc/rouge-github.css @@ -0,0 +1,199 @@ +.highlight table td { padding: 5px; } +.highlight table pre { margin: 0; } +.highlight .cm { + color: #999988; + font-style: italic; +} +.highlight .cp { + color: #999999; + font-weight: bold; +} +.highlight .c1 { + color: #999988; + font-style: italic; +} +.highlight .cs { + color: #999999; + font-weight: bold; + font-style: italic; +} +.highlight .c, .highlight .cd { + color: #999988; + font-style: italic; +} +.highlight .err { +} +.highlight .gd { + color: #000000; + background-color: #ffdddd; +} +.highlight .ge { + color: #000000; + font-style: italic; +} +.highlight .gr { + color: #aa0000; +} +.highlight .gh { + color: #999999; +} +.highlight .gi { + color: #000000; + background-color: #ddffdd; +} +.highlight .go { + color: #888888; +} +.highlight .gp { + color: #555555; +} +.highlight .gs { + font-weight: bold; +} +.highlight .gu { + color: #aaaaaa; +} +.highlight .gt { + color: #aa0000; +} +.highlight .kc { + font-weight: bold; +} +.highlight .kd { + font-weight: bold; +} +.highlight .kn { + font-weight: bold; +} +.highlight .kp { + font-weight: bold; +} +.highlight .kr { + font-weight: bold; +} +.highlight .kt { + color: #445588; + font-weight: bold; +} +.highlight .k, .highlight .kv { + font-weight: bold; +} +.highlight .mf { + color: #009999; +} +.highlight .mh { + color: #009999; +} +.highlight .il { + color: #009999; +} +.highlight .mi { + color: #009999; +} +.highlight .mo { + color: #009999; +} +.highlight .m, .highlight .mb, .highlight .mx { + color: #009999; +} +.highlight .sb { + color: #d14; +} +.highlight .sc { + color: #d14; +} +.highlight .sd { + color: #d14; +} +.highlight .s2 { + color: #d14; +} +.highlight .se { + color: #d14; +} +.highlight .sh { + color: #d14; +} +.highlight .si { + color: #d14; +} +.highlight .sx { + color: #d14; +} +.highlight .sr { + color: #009926; +} +.highlight .s1 { + color: #d14; +} +.highlight .ss { + color: #990073; +} +.highlight .s { + color: #d14; +} +.highlight .na { + color: #008080; +} +.highlight .bp { + color: #999999; +} +.highlight .nb { + color: #0086B3; +} +.highlight .nc { + color: #445588; + font-weight: bold; +} +.highlight .no { + color: #008080; +} +.highlight .nd { + color: #3c5d5d; + font-weight: bold; +} +.highlight .ni { + color: #800080; +} +.highlight .ne { + color: #990000; + font-weight: bold; +} +.highlight .nf { + color: #990000; + font-weight: bold; +} +.highlight .nl { + color: #990000; + font-weight: bold; +} +.highlight .nn { + color: #555555; +} +.highlight .nt { + color: #000080; +} +.highlight .vc { + color: #008080; +} +.highlight .vg { + color: #008080; +} +.highlight .vi { + color: #008080; +} +.highlight .nv { + color: #008080; +} +.highlight .ow { + font-weight: bold; +} +.highlight .o { + font-weight: bold; +} +.highlight .o { + font-weight: bold; +} +.highlight .w { + color: #bbbbbb; +} diff --git a/doc/rtti.adoc b/doc/rtti.adoc new file mode 100644 index 0000000..d5a8a6c --- /dev/null +++ b/doc/rtti.adoc @@ -0,0 +1,71 @@ + +## rtti + +### Synopsis + +Defined in . + +```c++ +namespace boost::openmethod::policies { + +struct rtti {}; + +} // boost::openmethod::policies +``` + +### Description + +The `rtti` facet provides type information for classes and objects, implements +downcast in presence of virtual inheritance, and writes descriptions of types to +an `ostream`-like object. + +### Requirements + +#### static_type + +```c++ +template +static auto static_type() -> type_id; +``` + +Returns a `type_id` for `Class`. + +#### dynamic_type + +```c++ +template +static auto dynamic_type(const Class& obj) -> type_id; +``` + +Returns a `type_id` for an object's dynamic type. + +#### type_name + +```c++ +template +static auto type_name(type_id type, Stream& stream) -> void; +``` + +Writes a description of `type` to `stream`. + +This requirement is optional. `rtti` provides a default implementation that writes `typeid({type})` to `stream`. + +#### type_index + +```c++ +static auto type_index(type_id type) -> /* unspecified */; +``` + +Returns a unique key for `type`. Required only for RTTI systems that assign more +than one type "identifiers" to a type. For example, standard RTTI allows +implementations to have multiple instances of `std::type_info` for the same +type. + +#### dynamic_cast_ref + +```c++ +template +static auto dynamic_cast_ref(B&& obj) -> D; +``` + +Casts `obj` to `D`. Required only if using virtual inheritance. diff --git a/doc/skin.png b/doc/skin.png new file mode 100644 index 0000000..23540cc Binary files /dev/null and b/doc/skin.png differ diff --git a/doc/smart_pointers.adoc b/doc/smart_pointers.adoc new file mode 100644 index 0000000..f56df9d --- /dev/null +++ b/doc/smart_pointers.adoc @@ -0,0 +1,21 @@ + +## Smart Pointers + +`virtual_ptr` can also be used in combination with smart pointers. +`virtual_ptr>` (aliased to `shared_virtual_ptr`) +and `virtual_ptr>` (aliased to +`unique_virtual_ptr`) deliver the convenience of automatic memory +management with the speed of `virtual_ptr`. Convenience functions +`make_shared_virtual` and `make_unique_virtual` create an object and return a +smart virtual_ptr to it. Since the exact type of the object is known, the vptr +is read from a static variable, without incuring the cost of a hash table +lookup. + +Here is a variaton of the AST example that uses dynamic allocation and unique +pointers: + + +[source,c++] +---- +include::{examplesdir}/ast_unique_ptr.cpp[tag=ast,indent=0] +---- diff --git a/doc/std_rtti.adoc b/doc/std_rtti.adoc new file mode 100644 index 0000000..0aa5cb8 --- /dev/null +++ b/doc/std_rtti.adoc @@ -0,0 +1,82 @@ + +## std_rtti + +### Synopsis + +Defined in . + +```c++ +namespace boost::openmethod::policies { + +struct std_rtti : virtual rtti { + template + static auto static_type() -> type_id; + + template + static auto dynamic_type(const Class& obj) -> type_id; + + template + static auto type_name(type_id type, Stream& stream) -> void; + + static auto type_index(type_id type) -> std::type_index; + + template + static auto dynamic_cast_ref(B&& obj) -> D; +}; + +} // boost::openmethod::policies +``` + +### Description + +`std_rtti` is an implementation of the `rtti` facet that uses standard RTTI. + +### Members + +#### static_type + +```c++ +template +static type_id static_type(); +``` + +Return the address of `Class`'s `type_info`, cast to a `type_id`. + +#### dynamic_type + +```c++ +template +static type_id dynamic_type(const Class& obj); +``` + +Return the address of `obj`{empty}'s `type_info`, cast to a `type_id`. + +#### type_name + +```c++ +template +static void type_name(type_id type, Stream& stream); +``` + +Write the demangled name of the class identified by `type` to `stream`. +Execute `stream << reinterpret_cast(type)->name()`. + +#### type_index + +```c++ +static /*unspecified*/ type_index(type_id type); +``` + +Return `std::type_index(*reinterpret_cast(type))`. + +The function is required because C++ does *not* guarantee that there is a single +instance of `std::type_info` for each specific type. + +#### dynamic_cast_ref + +```c++ +template +static Derived dynamic_cast_ref(Base&& obj); +``` + +Cast `obj` using the `dynamic_cast` operator. diff --git a/doc/throw_error_handler.adoc b/doc/throw_error_handler.adoc new file mode 100644 index 0000000..83df4f4 --- /dev/null +++ b/doc/throw_error_handler.adoc @@ -0,0 +1,33 @@ + +## throw_error_handler + +### Synopsis + +Defined in . + +```c++ +namespace boost::openmethod::policies { + +struct throw_error_handler : error_handler { + template + [[noreturn]] static auto error(const Error& error) -> void; +}; + +} // boost::openmethod::policies +``` + +### Description + +throw_error_handler is an implementation of the `error_handler` facet that +throws the error as an exception. + +### Members + +#### error + +```c++ +template +[[noreturn]] static auto error(const Error& error) -> void; +``` + +Throws `error`. diff --git a/doc/trace_output.adoc b/doc/trace_output.adoc new file mode 100644 index 0000000..e793a4c --- /dev/null +++ b/doc/trace_output.adoc @@ -0,0 +1,45 @@ + +## trace_output + +### Synopsis + +Defined in . + +```c++ +namespace boost::openmethod::policies { + +struct trace_output {}; + +} +``` + +--- + +### Description + +`trace_output` is a facet used to write trace messages. + +`initialize` can be directed to describe the classes and methods in a policy, +and how the dispatch tables are built, by including this facet in the policy, +and setting `trace_enabled` to `true`. The content and the format of the +description is not documented, beyond the guarantee that it provides an +exhaustive account of table construction, and may change between major, minor +and patch versions. + +### Requirements + +#### trace_enabled + +```c++ +static bool trace_enabled; +``` + +`true` if tracing is enabled, `false` otherwise. + +#### trace_stream + +```c++ +static RestrictedOutputStream trace_stream; +``` + +A static variable that satisfies the requirements of `RestrictedOutputStream`. diff --git a/doc/tutorial.adoc b/doc/tutorial.adoc new file mode 100644 index 0000000..0033e20 --- /dev/null +++ b/doc/tutorial.adoc @@ -0,0 +1,18 @@ + +[#tutorials] +# Tutorials +:toc: +:toc-title: +:idprefix: tutorials_ +:examplesdir: ../examples + +include::hello_world.adoc[] +include::multiple_dispatch.adoc[] +include::friendship.adoc[] +include::performance.adoc[] +include::smart_pointers.adoc[] +include::virtual_ptr_alt.adoc[] +include::core_api.adoc[] +include::policies.adoc[] +include::error_handling.adoc[] +include::custom_rtti.adoc[] diff --git a/doc/type_hash.adoc b/doc/type_hash.adoc new file mode 100644 index 0000000..1af6b64 --- /dev/null +++ b/doc/type_hash.adoc @@ -0,0 +1,98 @@ + +## type_hash + +### Synopsis + +Defined in . + +```c++ +namespace boost::openmethod::policies { + +struct type_hash {}; + +} // boost::openmethod::policies +``` + +### Description + +`type_hash` is a facet that provides a hash function for a fixed set of +`type_id`{empty}s. + +### Requirements + +### hash_type_id + +```c++ +static auto hash_type_id(type_id type) -> type_id; +``` + +Returns the hash of `type`. + +#### hash_initialize + +```c++ +template +static auto hash_initialize(ForwardIterator first, ForwardIterator last) -> Report; +``` + +Finds a hash function for the `type_id`{empty}s in the range `[first, last)`. +`ForwardIterator` is the same as in `vptr_vector::register_vptrs`. + +`hash_initialize` returns a `Report` object which is required to have two +members, `first` and `last`, which define the range `[first, last)` of the +possible output values of the hash function. + +## fast_perfect_hash + +### Synopsis + +Defined in . + +```c++ +class fast_perfect_hash : type_hash +{ + public: + static auto hash_type_id(type_id type) -> type_id; + template + static auto hash_initialize(ForwardIterator first, ForwardIterator last) -> Report; +}; +``` + +### Description + +`fast_perfect_hash` implements a very fast, perfect (but not minimal) hash +function for `type_id`{empty}s. + +### Members + +Find two factors + +#### hash_type_id + +```c++ +static auto hash_type_id(type_id type) -> type_id; +``` + +Returns `(type * M) >> S`, where `M` and `S` are factors found by +`hash_initialize`. + +If the policy has a `runtime_checks` facet, `hash_type_id` checks that `type` +corresponds to a registered class. If not, it reports a `unknown_class_error` +using the policy's error_handler facet, if present, then calls `abort`. + +#### hash_initialize + +```c++ +template +auto hash_initialize(ForwardIterator first, ForwardIterator last) -> Report; +``` + +Finds factors `M` and `S` such that `hash_type_id` is a collision-free hash +function. + +If no such factors cannot be found, `hash_initialize` reports a +`hash_search_error` using the policy's error_handler facet, if present, the +calls `abort`. + +If the policy has a `trace_output` facet, `hash_initialize` uses it to write a +summary of the search. diff --git a/doc/typedefs.adoc b/doc/typedefs.adoc new file mode 100644 index 0000000..c7d062a --- /dev/null +++ b/doc/typedefs.adoc @@ -0,0 +1,37 @@ + +## type_id + +### Synopsis + +Defined in ``. + +```c++ +namespace boost::openmethod { + +using type_id = std::uintptr_t; + +} +``` + +### Description + +`type_id` is an unsigned integer type used to identify types. It is wide enough +to contain a pointer. + +## vptr_type + +### Synopsis + +Defined in ``. + +```c++ +namespace boost::openmethod { + +using vptr_type = const /*unspecified*/ *; + +} +``` + +### Description + +`vptr_type` is the type of a pointer to a v-table. diff --git a/doc/use_classes.adoc b/doc/use_classes.adoc new file mode 100644 index 0000000..eb3f2cb --- /dev/null +++ b/doc/use_classes.adoc @@ -0,0 +1,58 @@ + +## use_classes + +### Synopsis + +Defined in . + +```c++ +namespace boost::openmethod { + +template +struct use_classes { + use_classes(); + ~use_classes(); +}; + +} +``` + +Usage: + +```c++ +use_classes some_unique_name; // at file scope +``` + +### Description + +`use_classes`, instantiated as a static object, registers `Classes` in `Policy`. + +Classes potentially involved in a method definition, an overrider, or a method +call must be registered via `use_classes`. A class may be registered multiple +times. A class and its direct bases must be listed together in one or more +instantiations of `use_classes`. + +Virtual and multiple inheritance are supported, as long as they don't result in +a class lattice that contains repeated inheritance. + +NOTE: The default value for `Policy` is the value of +`BOOST_OPENMETHOD_DEFAULT_POLICY` when `` is +included. Subsequently changing it has no retroactive effect. + +### Members + +#### constructor + +```c++ +use_classes(); +``` + +Registers `Classes` and their inheritance relationships in `Policy`. + +#### destructor + +```c++ +~use_classes(); +``` + +Removes `Classes` and their inheritance relationships from `Policy`. diff --git a/doc/vector_vptr.adoc b/doc/vector_vptr.adoc new file mode 100644 index 0000000..5c6d459 --- /dev/null +++ b/doc/vector_vptr.adoc @@ -0,0 +1,58 @@ + +## vptr_vector + +### Synopsis + +Defined in . + +```c++ +namespace boost::openmethod::policies { + +template +class vptr_vector : Base { + public: + template + static auto register_vptrs(ForwardIterator first, ForwardIterator last) -> void; + + template + static auto dynamic_vptr(const Class& arg) -> const vptr_type&; +}; + +} +``` + +### Description + +`vptr_vector` is an implementation or `external_vptr` that keeps the pointers +to the v-tables in a `std::vector`. If `UseIndirectVptrs` is `indirect_vptr`, +stores pointers to pointers to the v-tables. + +`Policy` is the policy containing the facet. + +### Members + +#### register_vptrs + +```c++ +template +auto register_vptrs(ForwardIterator first, ForwardIterator last) -> void; +``` + +Stores the pointers to v-tables in a vector, indexed by the (possibly hashed) +`type_id`s of the classes registered in `Policy`. + +If `Policy` contains a `type_hash` facet, call its `hash_initialize` +function, and uses it to convert the `type_id`{empty}s to an index. + +#### dynamic_vptr + +```c++ +template +auto dynamic_vptr(const Class& object) -> const vptr_type&; +``` + +Returns a pointer to the v-table for `object` (by reference). + +Obtains a `type_id` for `object` using `Policy::dynamic_type`. If _Policy_ +contains a `type_hash` facet, uses it to convert the result to an index; +otherwise, uses the `type_id` as the index. diff --git a/doc/vectored_error_handler.adoc b/doc/vectored_error_handler.adoc new file mode 100644 index 0000000..10b5625 --- /dev/null +++ b/doc/vectored_error_handler.adoc @@ -0,0 +1,52 @@ + +## vectored_error_handler + +### Synopsis + +Defined in . + +```c++ +namespace boost::openmethod::policies { + +template +class vectored_error_handler : public error_handler { + public: + using error_variant = std::variant< + openmethod_error, not_implemented_error, unknown_class_error, + hash_search_error, type_mismatch_error, static_slot_error, + static_stride_error>; + using function_type = std::function; + + template + static auto error(const Error& error) -> void; + static auto set_error_handler(error_handler_type handler) -> function_type; +}; + +} +``` + +### Description + +`vectored_error_handler` is an implementation of `error_handler` that calls a +`std::function` to handle the error. + +### Members + +#### error + +```c++ +template +static auto error(const Error& error) -> void; +``` + +Calls the function last set via `set_error_handler` or, if it was never called, +and if _Policy_ contains an `error_output` facet, use it to print a description +of `error`. + +#### error + +```c++ +static auto set_error_handler(function_type handler) -> function_type; +``` + +Sets `handler` as the function to call in case of error. diff --git a/doc/virtual_.adoc b/doc/virtual_.adoc new file mode 100644 index 0000000..6ef8d6e --- /dev/null +++ b/doc/virtual_.adoc @@ -0,0 +1,24 @@ + + +## virtual_ + +### Synopsis + +Defined in ``. + +```c++ +namespace boost::openmethod { + +template +struct virtual_; + +} +``` + +### Description + +Marks a formal parameter of a method as virtual. Requires a specialization of +`virtual_traits` for `T` and the `Policy` of the method. Specializations for +`T&`, `T&&`, `std::unique_ptr`, `std::shared_ptr` and `const +std::shared_ptr&` are provided. See the documentation of `virtual_traits` for +more information. diff --git a/doc/virtual_ptr.adoc b/doc/virtual_ptr.adoc new file mode 100644 index 0000000..88b3f98 --- /dev/null +++ b/doc/virtual_ptr.adoc @@ -0,0 +1,289 @@ + +[#virtual_ptr] +:idprefix: virtual_ptr_ + +## virtual_ptr + +### Synopsis + +`virtual_ptr` is defined in ``. + +```c++ +namespace boost::openmethod { + +template +class virtual_ptr { + public: + static constexpr bool is_smart_ptr = /* see below */; + using element_type = /* see below */; + + template virtual_ptr(Other& other); + template virtual_ptr(const Other& other); + template virtual_ptr(Other&& other); + + template + static auto final(Other&& obj); + + auto get() const -> element_type*; + auto operator->() const -> element_type*; + auto operator*() const -> element_type&; + auto pointer() const -> const Class*&; + + template + auto cast() const -> virtual_ptr; +}; + +template +virtual_ptr(Class&) -> virtual_ptr; + +template +inline auto final_virtual_ptr(Class& obj) -> virtual_ptr< + Class, BOOST_OPENMETHOD_DEFAULT_POLICY>; + +template +inline auto final_virtual_ptr(Class& obj) -> virtual_ptr; + +template +bool operator==( + const virtual_ptr& left, + const virtual_ptr& right); + +template +bool operator!=( + const virtual_ptr& left, + const virtual_ptr& right); + +} // namespace boost::openmethod +``` + +Defined in ``: + +```c++ +namespace boost::openmethod { + +template +using shared_virtual_ptr = virtual_ptr, Policy>; + +template< + class Class, class Policy = BOOST_OPENMETHOD_DEFAULT_POLICY, typename... T> +inline auto make_shared_virtual(T&&... args); + +} +``` +Defined in ``: + +```c++ +namespace boost::openmethod { + +template +using unique_virtual_ptr = virtual_ptr, Policy>; + +template< + class Class, class Policy = BOOST_OPENMETHOD_DEFAULT_POLICY, typename... T> +inline auto make_unique_virtual(T&&... args); + +} +``` + +### Description + +`virtual_ptr` is a wide pointer that combines a pointer to an object and a +pointer to its v-table. The object pointer can be a plain pointer or a smart +pointer. Specializations of `virtual_traits` are required for smart pointers. +They are provided for `std::unique_ptr` and `std::shared_ptr`. + +A plain `virtual_ptr` can be constructed from a reference, a smart pointer, or +another `virtual_ptr`. A smart `virtual_ptr` can be constructed from a smart +pointer or from a smart `virtual_ptr`. Usual conversions - from derived to base, +and from non-const to const - are allowed. + +`virtual_ptr` does not have a default constructor, nor a "null" state. In that +respect, it behaves more like a reference than a pointer. The only reason why it +is not called `virtual_ref` is to save the name for the day C++ will support +smart references. + +### Members + +#### is_smart_ptr + +```c++ +static constexpr bool is_smart_ptr; +``` + +`true` if `Class` is a smart pointer, `false` otherwise. The value is derived +from `virtual_traits`: if it has a member template called +`rebind`, `Class` is considered a smart pointer. + +#### element_type + +```c++ +using element_type = std::conditional_t< + is_smart_ptr, typename Class::element_type, Class>; +``` + +The class of the object pointed to. + +#### constructors + +[source,c++] +---- +template virtual_ptr(Other& other); // 1 +template virtual_ptr(const Other& other); // 2 +template virtual_ptr(Other&& other); // 3 +---- + +(1), (2) If `virtual_ptr` uses a plain pointer, `other` must be a lvalue +reference to an object of a registered class, or to a `virtual_ptr` (plain or +smart). If `virtual_ptr` uses a smart pointer, `other` must be a reference to a smart +pointer, or a smart `virtual_ptr`. + +(3) Smart `virtual_ptr` only. Constructs a `virtual_ptr` from a smart pointer or +a smart `virtual_ptr`. The (smart) object pointer is moved from `other`. + +If `other` is also a `virtual_ptr`, the v-table pointer is copied from it. +Otherwise, it is deduced from the object. The `Policy` must be the same for both +`virtual_ptr`{empty}s. + + +#### final + +```c++ +template +static auto final(Other&& obj); +``` + +Constructs a `virtual_ptr` from a reference to an object, or from a smart +pointer. It is assumed that the static and dynamic types are the same. The +v-table pointer is initialized from the `Policy::static_vptr` for the class, +which needs not be polymorphic. + +#### get + +```c++ +auto get() const -> element_type*; +``` + +Returns a pointer to the object. + +#### operator-> + +```c++ +auto operator->() const -> element_type*; +``` + +Returns a pointer to the object. + +#### operator* + +```c++ +auto operator*() const -> element_type&; +``` + +Returns a reference to the object. + +#### pointer + +```c++ +auto pointer() const; +``` + +Returns a reference to the object pointer, which can be either a plain pointer +or a smart pointer. + +#### cast + +```c++ +template +auto cast() const -> virtual_ptr; +``` + +Returns a `virtual_ptr` to the same object, cast to `Other`. + +### Deduction guide + +```c++ +template +virtual_ptr(Class&) -> virtual_ptr; +``` + +--- + +### Non-members + +#### virtual_shared_ptr + +```c++ +template +using virtual_shared_ptr = virtual_ptr, Policy>; +``` + +Convenience alias for `virtual_ptr, Policy>`. + +#### virtual_unique_ptr + +```c++ +template +using virtual_unique_ptr = virtual_ptr, Policy>; +``` + +Convenience alias for `virtual_ptr, Policy>`. + +#### final_virtual_ptr + +```c++ +template +inline auto final_virtual_ptr(Class&& obj); + +template +inline auto final_virtual_ptr(Class&& obj); +``` + +Utility functions, forwarding to `virtual_ptr::final`. + +If `Policy` is not specified, `BOOST_OPENMETHOD_DEFAULT_POLICY` is used. + +#### make_virtual_shared + +```c++ +template< + class Class, class Policy = BOOST_OPENMETHOD_DEFAULT_POLICY, typename... T> +inline auto make_virtual_shared(T&&... args); +``` + +Creates an object using `std::make_shared` and returns a `virtual_shared_ptr` to +it. The v-table pointer is initialized from the the `Policy::static_vptr` for +the class, which needs not be polymorphic. + +#### make_virtual_unique + +```c++ +template< + class Class, class Policy = BOOST_OPENMETHOD_DEFAULT_POLICY, typename... T> +inline auto make_virtual_unique(T&&... args); +``` + +Creates an object using `std::make_unique` and returns a `virtual_unique_ptr` to +it. The v-table pointer is initialized from the the `Policy::static_vptr` for +the class, which needs not be polymorphic. + +#### operator== + +```c++ +template +bool operator==( + const virtual_ptr& left, + const virtual_ptr& right); +``` + +Compares two `virtual_ptr` objects for equality. + +#### operator!= + +```c++ +template +bool operator!=( + const virtual_ptr& left, + const virtual_ptr& right); +``` + +Compares two `virtual_ptr` objects for inequality. diff --git a/doc/virtual_ptr_alt.adoc b/doc/virtual_ptr_alt.adoc new file mode 100644 index 0000000..e4f0573 --- /dev/null +++ b/doc/virtual_ptr_alt.adoc @@ -0,0 +1,76 @@ + +## Alternatives to virtual_ptr + +Virtual arguments can be passed as plain references. In a method declaration, +parameters with a type decorated with `virtual_` are considered in overrider +selection (along with `virtual_ptr` parameters). + +For example, the `poke` open-method in the Animals example can be rewritten as: + +[source,c++] +---- +include::{examplesdir}/virtual_.cpp[tag=virtual_parameter,indent=0] + +int main() { + boost::openmethod::initialize(); + + Cat cat; + poke(std::cout, cat); // hiss +} +---- + +Note that `virtual_` is not used in the overrider. It is also removed from the +method's signature. + +By itself, `virtual_` does not provide any benefits. Passing the virtual +argument by reference almost compiles to the same code as creating a +`virtual_ptr`, using it for one call, then throwing it way. The only difference +is that the virtual argument is passed as one pointer instead of two. + +However, we can now customize how the vptr is obtained. When the method sees a +`virtual_` parameter, it looks for a `boost_openmethod_vptr` function that takes +the parameter (by const reference), and returns a `vptr_type`. If one is found, +it is called to obtain the vptr. The vptr for a specific registered class can be +obtained via a variable template `static_vptr`, nested in class `default_policy` +(more on policies below). + +In the following example, we embed a vptr in the object, just like the vptr for +native virtual functions: + +[source,c++] +---- +include::{examplesdir}/virtual_.cpp[tag=virtual_intrusive,indent=0] + +int main() { + boost::openmethod::initialize(); + + Cat cat; + poke(std::cout, cat); // hiss +} +---- + +NOTE: With this approach, classes need not be polymorphic. A virtual +destructor might be needed for correct destruction of objects, but it is not +required by the library. + +The `with_vptr` CRTP class automates the creation and management of embedded +vptrs. + +[source,c++] +---- +include::{examplesdir}/virtual_.cpp[tag=with_vptr,indent=0] + +int main() { + boost::openmethod::initialize(); + + Cat cat; + poke(std::cout, cat); // hiss +} +---- + +If `with_vptr` is passed only the class being defined, it adds a vptr to it, and +defines a `boost_openmethod_vptr` friend function. If more classes are passed, +they must be the direct bases of the class potentially involved in open-method +calls. Its constructor and destructor set the vptr to point to the v-table for +the class. `with_vptr` also takes care of registering the classes, so this time +the call to `BOOST_OPENMETHOD_CLASSES` is not needed. diff --git a/doc/virtual_traits.adoc b/doc/virtual_traits.adoc new file mode 100644 index 0000000..1b786b3 --- /dev/null +++ b/doc/virtual_traits.adoc @@ -0,0 +1,92 @@ + +## virtual_traits + +### Synopsis + +Defined in . + +```c++ +namespace boost::openmethod { + +template +struct virtual_traits; // not defined + +template +struct virtual_traits<..., Policy> { + using virtual_type = ...; + static auto peek(const T& arg) -> const ...&; + template static auto cast(T& obj) -> ...; + template using rebind = ...; // for smart virtual pointers +}; + +} +``` + +### Description + +Specializations of `virtual_traits` provide an interface for `method` and +`virtual_ptr` to manipulate virtual arguments. + +### Specializations + +Specializations are provided for: + +* `virtual_ptr` +* `const virtual_ptr&` +* `T&` +* `T&&` +* `std::shared_ptr`: defined in +* `const std::shared_ptr&`: defined in +* `std::unique_ptr`: defined in + +### Members + +#### virtual_type + +```c++ +using virtual_type = ...; +``` + +The class used for method selection. It must be registered in Policy. + +For example, `virtual_type` in the following specializations are all `Class`: + +* `virtual_traits>` +* `virtual_traits&, Policy>` +* `virtual_traits` +* `virtual_traits&, Policy>` + +#### peek + +```c++ +static auto peek(T arg) -> const ...&; +``` + +Returns a value for the purpose of obtaining a v-table pointer for `arg`. + +For example, `peek` returns a `const T&` for a `T&`, a `const T&`, a `T&&`, and +a `std::shared_ptr`; and a `const virtual_ptr&` for a +`const virtual_ptr&`. + + +#### cast + +```c++ +template +static decltype(auto) cast(T& obj); +``` + +Casts argument `obj` to the type expected by an overrider. + +For example, if a method takes a `virtual_`, an overrider for `Cat&` +uses `virtual_traits` to cast a `Animal&` to a `Cat&`. + +#### rebind + +```c++ +template using rebind = ...; +``` + +For smart pointers only. Rebinds the smart pointer to a different type. For +example, `virtual_traits, Policy>::rebind` is +`std::shared_ptr`. diff --git a/doc/vptr.adoc b/doc/vptr.adoc new file mode 100644 index 0000000..ef89646 --- /dev/null +++ b/doc/vptr.adoc @@ -0,0 +1,110 @@ + +## vptr + +### Synopsis + +Defined in . + +```c++ +namespace boost::openmethod::policies { + +struct vptr {}; + +} +``` + +### Description + +`vptr` is a facet that obtains a pointer to the v-table for an object. + +OpenMethod implements method dispatch in a way similar to native virtual +function dispatch: for each virtual argument, fetch a pointer to the dispatch +data (known as the v-table), and use it to select a pointer to a function. +OpenMethod v-tables contain pointers to functions for unary methods, and, for +multi-methods, pointers to, and coordinates in, a multi-dimensional table of +pointers to functions. + +The `vptr` facet is used during method call to fetch the vptr for virtual +arguments corresponding to the `virtual_` parameters in the method +declaration. It is also used by the constructor of `virtual_ptr` to obtain a +vptr on the basis of an object's dynamic type. + +`virtual_ptr::final`, and the related convenience functions, assume that the +static and dynamic types of their argument are the same. The vptr is obtained +statically from the policy's `static_vptr` member. It is conceivable +to organize an entire program around the "final" constructs; thus, the `vptr` +facet is optional. + +### Requirements + +#### dynamic_vptr; + +```c++ +template +static auto dynamic_vptr(const Class& obj) -> const vptr_type&; +``` + +Returns a pointer to the v-table for `obj`. + +NOTE: `dynamic_vptr` _must_ return a reference to the pointer, not a value. This +is required for indirect `virtual_ptr`{empty}s. + +## extern_vptr + +### Synopsis + +```c++ +struct extern_vptr : virtual vptr {}; +``` + +### Description + +`extern_vptr` is a specialization of `vptr` that stores vptrs outside of the +objects. + +### Requirements + +The requirements of `vptr`, plus the following. + +#### register_vptrs + +```c++ +template +auto register_vptrs(ForwardIterator first, ForwardIterator last) -> void; +``` + +`ForwardIterator` is a forward iterator over a range of objects that contain +information about the type ids and the vptr of a registered class. They have the +following member functions: + +```c++ +auto type_id_begin() const -> type_id_forward_iterator; +auto type_id_end() const -> type_id_forward_iterator; +auto vptr() const -> const vptr_type&; +``` + +`type_id_begin` and `type_id_end` return iterators delimiting a range of +`type_id`s for the class. + +`vptr` returns a _reference_ to a _static_ variable containing a pointer to the +v-table for a registered class. Its value is set by `initialize`. While the +value of the variable changes with each call to `initialize`, the variable +itself remains the same. + +## indirect_vptr + +### Synopsis + +```c++ +struct indirect_vptr {}; +``` + +### Description + +`indirect_vptr` is a facet that makes `virtual_ptr`{empty}s and `with_vptr` use +pointers to pointers to v-tables, instead of straight pointers. As a +consequence, they remain valid after a call to `initialize`. + +### Requirements + +None. The facet is its own implementation. diff --git a/doc/with_vptr.adoc b/doc/with_vptr.adoc new file mode 100644 index 0000000..0628c01 --- /dev/null +++ b/doc/with_vptr.adoc @@ -0,0 +1,77 @@ + +## with_vptr + +### Synopsis + +Defined in . + +```c++ +namespace boost::openmethod { + +template +class with_vptr { + protected: + with_vptr(); + ~with_vptr(); + friend auto boost_openmethod_vptr(const Class& obj) -> vptr_type; +}; + +template +class with_vptr { + protected: + with_vptr(); + ~with_vptr(); + friend auto boost_openmethod_vptr(const Class& obj) -> vptr_type; + // if sizeof(MoreBases...) > 0 +}; + +} // namespace boost::openmethod +``` + +### Description + +`with_vptr` is a CRTP class template that embeds and manages a vptr across a +class hierarchy. + +If `Class` has no `Bases`, `with_vptr` adds a `boost_openmethod_vptr` private +member to `Class`. In either case, it sets the vptr to the v-table of `Class` +from `Policy`. It also creates a `boost_openmethod_vptr` friend function that +takes a a `const Class&` and returns the embedded vptr. + +If `Class` has has more than one base, the `boost_openmethod_vptr` friend +function is also created. It returns one of the embedded vptrs (it doesn't +matter which one, as they all have the same value). This is to resolve +ambiguities + +As part of its implementation, `with_vptr` may also declare one or two free +functions (`boost_openmethod_policy` and `boost_openmethod_bases`) at certain +levels of the hierarchy. + +### Members + +#### constructor + +```c++ +with_vptr(); +``` + +Sets the vptr to the v-table for Class, obtained from `Policy`. If `Policy` +contains `indirect_vptr`, an additional level of indirection is added, thus +preserving the validity of the pointer across calls to `initialize`. + + +#### destructor + +```c++ +~with_vptr(); +``` + +For each `Base`, sets the vptr to the v-table for that base. + +#### Free Functions + +```c++ +auto boost_openmethod_vptr(const Class& obj) -> vptr_type; +``` + +Returns the vptr embedded in `obj`. diff --git a/doc/zajo-dark.css b/doc/zajo-dark.css new file mode 100644 index 0000000..4247e85 --- /dev/null +++ b/doc/zajo-dark.css @@ -0,0 +1,478 @@ +/* Asciidoctor default stylesheet | MIT License | http://asciidoctor.org */ +/* Uncomment @import statement below to use as custom stylesheet */ +/*@import "https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700";*/ + +/* Zajo's custom font import. The rest of the customizations are at the bottom of this css file, which is otherwise kept unchanged */ +@import "https://fonts.googleapis.com/css?family=Anonymous+Pro|Istok+Web|Quicksand|Poiret+One"; + +article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block} +audio,canvas,video{display:inline-block} +audio:not([controls]){display:none;height:0} +script{display:none!important} +html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%} +a{background:transparent} +a:focus{outline:thin dotted} +a:active,a:hover{outline:0} +h1{font-size:2em;margin:.67em 0} +abbr[title]{border-bottom:1px dotted} +b,strong{font-weight:bold} +dfn{font-style:italic} +hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0} +mark{background:#ff0;color:#000} +code,kbd,pre,samp{font-family:monospace;font-size:1em} +pre{white-space:pre-wrap} +q{quotes:"\201C" "\201D" "\2018" "\2019"} +small{font-size:80%} +sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline} +sup{top:-.5em} +sub{bottom:-.25em} +img{border:0} +svg:not(:root){overflow:hidden} +figure{margin:0} +fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em} +legend{border:0;padding:0} +button,input,select,textarea{font-family:inherit;font-size:100%;margin:0} +button,input{line-height:normal} +button,select{text-transform:none} +button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer} +button[disabled],html input[disabled]{cursor:default} +input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0} +button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0} +textarea{overflow:auto;vertical-align:top} +table{border-collapse:collapse;border-spacing:0} +*,*::before,*::after{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box} +html,body{font-size:100%} +body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;font-weight:400;font-style:normal;line-height:1;position:relative;cursor:auto;tab-size:4;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased} +a:hover{cursor:pointer} +img,object,embed{max-width:100%;height:auto} +object,embed{height:100%} +img{-ms-interpolation-mode:bicubic} +.left{float:left!important} +.right{float:right!important} +.text-left{text-align:left!important} +.text-right{text-align:right!important} +.text-center{text-align:center!important} +.text-justify{text-align:justify!important} +.hide{display:none} +img,object,svg{display:inline-block;vertical-align:middle} +textarea{height:auto;min-height:50px} +select{width:100%} +.center{margin-left:auto;margin-right:auto} +.stretch{width:100%} +.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em} +div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0;direction:ltr} +a{color:#2156a5;text-decoration:underline;line-height:inherit} +a:hover,a:focus{color:#1d4b8f} +a img{border:none} +p{font-family:inherit;font-weight:400;font-size:1em;line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility} +p aside{font-size:.875em;line-height:1.35;font-style:italic} +h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em} +h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0} +h1{font-size:2.125em} +h2{font-size:1.6875em} +h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em} +h4,h5{font-size:1.125em} +h6{font-size:1em} +hr{border:solid #dddddf;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em;height:0} +em,i{font-style:italic;line-height:inherit} +strong,b{font-weight:bold;line-height:inherit} +small{font-size:60%;line-height:inherit} +code{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;color:rgba(0,0,0,.9)} +ul,ol,dl{font-size:1em;line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit} +ul,ol{margin-left:1.5em} +ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0;font-size:1em} +ul.square li ul,ul.circle li ul,ul.disc li ul{list-style:inherit} +ul.square{list-style-type:square} +ul.circle{list-style-type:circle} +ul.disc{list-style-type:disc} +ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0} +dl dt{margin-bottom:.3125em;font-weight:bold} +dl dd{margin-bottom:1.25em} +abbr,acronym{text-transform:uppercase;font-size:90%;color:rgba(0,0,0,.8);border-bottom:1px dotted #ddd;cursor:help} +abbr{text-transform:none} +blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd} +blockquote cite{display:block;font-size:.9375em;color:rgba(0,0,0,.6)} +blockquote cite::before{content:"\2014 \0020"} +blockquote cite a,blockquote cite a:visited{color:rgba(0,0,0,.6)} +blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)} +@media screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2} +h1{font-size:2.75em} +h2{font-size:2.3125em} +h3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em} +h4{font-size:1.4375em}} +table{background:#fff;margin-bottom:1.25em;border:solid 1px #dedede} +table thead,table tfoot{background:#f7f8f7} +table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left} +table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)} +table tr.even,table tr.alt,table tr:nth-of-type(even){background:#f8f8f7} +table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{display:table-cell;line-height:1.6} +h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2;word-spacing:-.05em} +h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400} +.clearfix::before,.clearfix::after,.float-group::before,.float-group::after{content:" ";display:table} +.clearfix::after,.float-group::after{clear:both} +*:not(pre)>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background-color:#f7f7f8;-webkit-border-radius:4px;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed;word-wrap:break-word} +*:not(pre)>code.nobreak{word-wrap:normal} +*:not(pre)>code.nowrap{white-space:nowrap} +pre,pre>code{line-height:1.45;color:rgba(0,0,0,.9);font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;text-rendering:optimizeSpeed} +em em{font-style:normal} +strong strong{font-weight:400} +.keyseq{color:rgba(51,51,51,.8)} +kbd{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background-color:#f7f7f7;border:1px solid #ccc;-webkit-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em white inset;box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em #fff inset;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap} +.keyseq kbd:first-child{margin-left:0} +.keyseq kbd:last-child{margin-right:0} +.menuseq,.menuref{color:#000} +.menuseq b:not(.caret),.menuref{font-weight:inherit} +.menuseq{word-spacing:-.02em} +.menuseq b.caret{font-size:1.25em;line-height:.8} +.menuseq i.caret{font-weight:bold;text-align:center;width:.45em} +b.button::before,b.button::after{position:relative;top:-1px;font-weight:400} +b.button::before{content:"[";padding:0 3px 0 2px} +b.button::after{content:"]";padding:0 2px 0 3px} +p a>code:hover{color:rgba(0,0,0,.9)} +#header,#content,#footnotes,#footer{width:100%;margin-left:auto;margin-right:auto;margin-top:0;margin-bottom:0;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em} +#header::before,#header::after,#content::before,#content::after,#footnotes::before,#footnotes::after,#footer::before,#footer::after{content:" ";display:table} +#header::after,#content::after,#footnotes::after,#footer::after{clear:both} +#content{margin-top:1.25em} +#content::before{content:none} +#header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0} +#header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #dddddf} +#header>h1:only-child,body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #dddddf;padding-bottom:8px} +#header .details{border-bottom:1px solid #dddddf;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:-ms-flexbox;display:-webkit-flex;display:flex;-ms-flex-flow:row wrap;-webkit-flex-flow:row wrap;flex-flow:row wrap} +#header .details span:first-child{margin-left:-.125em} +#header .details span.email a{color:rgba(0,0,0,.85)} +#header .details br{display:none} +#header .details br+span::before{content:"\00a0\2013\00a0"} +#header .details br+span.author::before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)} +#header .details br+span#revremark::before{content:"\00a0|\00a0"} +#header #revnumber{text-transform:capitalize} +#header #revnumber::after{content:"\00a0"} +#content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #dddddf;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem} +#toc{border-bottom:1px solid #e7e7e9;padding-bottom:.5em} +#toc>ul{margin-left:.125em} +#toc ul.sectlevel0>li>a{font-style:italic} +#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0} +#toc ul{font-family:"Open Sans","DejaVu Sans",sans-serif;list-style-type:none} +#toc li{line-height:1.3334;margin-top:.3334em} +#toc a{text-decoration:none} +#toc a:active{text-decoration:underline} +#toctitle{color:#7a2518;font-size:1.2em} +@media screen and (min-width:768px){#toctitle{font-size:1.375em} +body.toc2{padding-left:15em;padding-right:0} +#toc.toc2{margin-top:0!important;background-color:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #e7e7e9;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto} +#toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em} +#toc.toc2>ul{font-size:.9em;margin-bottom:0} +#toc.toc2 ul ul{margin-left:0;padding-left:1em} +#toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em} +body.toc2.toc-right{padding-left:0;padding-right:15em} +body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #e7e7e9;left:auto;right:0}} +@media screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0} +#toc.toc2{width:20em} +#toc.toc2 #toctitle{font-size:1.375em} +#toc.toc2>ul{font-size:.95em} +#toc.toc2 ul ul{padding-left:1.25em} +body.toc2.toc-right{padding-left:0;padding-right:20em}} +#content #toc{border-style:solid;border-width:1px;border-color:#e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;-webkit-border-radius:4px;border-radius:4px} +#content #toc>:first-child{margin-top:0} +#content #toc>:last-child{margin-bottom:0} +#footer{max-width:100%;background-color:rgba(0,0,0,.8);padding:1.25em} +#footer-text{color:rgba(255,255,255,.8);line-height:1.44} +#content{margin-bottom:.625em} +.sect1{padding-bottom:.625em} +@media screen and (min-width:768px){#content{margin-bottom:1.25em} +.sect1{padding-bottom:1.25em}} +.sect1:last-child{padding-bottom:0} +.sect1+.sect1{border-top:1px solid #e7e7e9} +#content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400} +#content h1>a.anchor::before,h2>a.anchor::before,h3>a.anchor::before,#toctitle>a.anchor::before,.sidebarblock>.content>.title>a.anchor::before,h4>a.anchor::before,h5>a.anchor::before,h6>a.anchor::before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em} +#content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible} +#content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none} +#content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221} +.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em} +.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:"Noto Serif","DejaVu Serif",serif;font-size:1rem;font-style:italic} +table.tableblock.fit-content>caption.title{white-space:nowrap;width:0} +.paragraph.lead>p,#preamble>.sectionbody>[class="paragraph"]:first-of-type p{font-size:1.21875em;line-height:1.6;color:rgba(0,0,0,.85)} +table.tableblock #preamble>.sectionbody>[class="paragraph"]:first-of-type p{font-size:inherit} +.admonitionblock>table{border-collapse:separate;border:0;background:none;width:100%} +.admonitionblock>table td.icon{text-align:center;width:80px} +.admonitionblock>table td.icon img{max-width:none} +.admonitionblock>table td.icon .title{font-weight:bold;font-family:"Open Sans","DejaVu Sans",sans-serif;text-transform:uppercase} +.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #dddddf;color:rgba(0,0,0,.6)} +.admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0} +.exampleblock>.content{border-style:solid;border-width:1px;border-color:#e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;-webkit-border-radius:4px;border-radius:4px} +.exampleblock>.content>:first-child{margin-top:0} +.exampleblock>.content>:last-child{margin-bottom:0} +.sidebarblock{border-style:solid;border-width:1px;border-color:#e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;-webkit-border-radius:4px;border-radius:4px} +.sidebarblock>:first-child{margin-top:0} +.sidebarblock>:last-child{margin-bottom:0} +.sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center} +.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0} +.literalblock pre,.listingblock pre:not(.highlight),.listingblock pre[class="highlight"],.listingblock pre[class^="highlight "],.listingblock pre.CodeRay,.listingblock pre.prettyprint{background:#f7f7f8} +.sidebarblock .literalblock pre,.sidebarblock .listingblock pre:not(.highlight),.sidebarblock .listingblock pre[class="highlight"],.sidebarblock .listingblock pre[class^="highlight "],.sidebarblock .listingblock pre.CodeRay,.sidebarblock .listingblock pre.prettyprint{background:#f2f1f1} +.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{-webkit-border-radius:4px;border-radius:4px;word-wrap:break-word;overflow-x:auto;padding:1em;font-size:.8125em} +@media screen and (min-width:768px){.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{font-size:.90625em}} +@media screen and (min-width:1280px){.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{font-size:1em}} +.literalblock pre.nowrap,.literalblock pre.nowrap pre,.listingblock pre.nowrap,.listingblock pre.nowrap pre{white-space:pre;word-wrap:normal} +.literalblock.output pre{color:#f7f7f8;background-color:rgba(0,0,0,.9)} +.listingblock pre.highlightjs{padding:0} +.listingblock pre.highlightjs>code{padding:1em;-webkit-border-radius:4px;border-radius:4px} +.listingblock pre.prettyprint{border-width:0} +.listingblock>.content{position:relative} +.listingblock code[data-lang]::before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:#999} +.listingblock:hover code[data-lang]::before{display:block} +.listingblock.terminal pre .command::before{content:attr(data-prompt);padding-right:.5em;color:#999} +.listingblock.terminal pre .command:not([data-prompt])::before{content:"$"} +table.pyhltable{border-collapse:separate;border:0;margin-bottom:0;background:none} +table.pyhltable td{vertical-align:top;padding-top:0;padding-bottom:0;line-height:1.45} +table.pyhltable td.code{padding-left:.75em;padding-right:0} +pre.pygments .lineno,table.pyhltable td:not(.code){color:#999;padding-left:0;padding-right:.5em;border-right:1px solid #dddddf} +pre.pygments .lineno{display:inline-block;margin-right:.25em} +table.pyhltable .linenodiv{background:none!important;padding-right:0!important} +.quoteblock{margin:0 1em 1.25em 1.5em;display:table} +.quoteblock>.title{margin-left:-1.5em;margin-bottom:.75em} +.quoteblock blockquote,.quoteblock p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify} +.quoteblock blockquote{margin:0;padding:0;border:0} +.quoteblock blockquote::before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)} +.quoteblock blockquote>.paragraph:last-child p{margin-bottom:0} +.quoteblock .attribution{margin-top:.75em;margin-right:.5ex;text-align:right} +.verseblock{margin:0 1em 1.25em} +.verseblock pre{font-family:"Open Sans","DejaVu Sans",sans;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility} +.verseblock pre strong{font-weight:400} +.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex} +.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic} +.quoteblock .attribution br,.verseblock .attribution br{display:none} +.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.025em;color:rgba(0,0,0,.6)} +.quoteblock.abstract blockquote::before,.quoteblock.excerpt blockquote::before,.quoteblock .quoteblock blockquote::before{display:none} +.quoteblock.abstract blockquote,.quoteblock.abstract p,.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{line-height:1.6;word-spacing:0} +.quoteblock.abstract{margin:0 1em 1.25em;display:block} +.quoteblock.abstract>.title{margin:0 0 .375em;font-size:1.15em;text-align:center} +.quoteblock.excerpt,.quoteblock .quoteblock{margin:0 0 1.25em;padding:0 0 .25em 1em;border-left:.25em solid #dddddf} +.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{color:inherit;font-size:1.0625rem} +.quoteblock.excerpt .attribution,.quoteblock .quoteblock .attribution{color:inherit;text-align:left;margin-right:0} +table.tableblock{max-width:100%;border-collapse:separate} +p.tableblock:last-child{margin-bottom:0} +td.tableblock>.content{margin-bottom:-1.25em} +table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede} +table.grid-all>thead>tr>.tableblock,table.grid-all>tbody>tr>.tableblock{border-width:0 1px 1px 0} +table.grid-all>tfoot>tr>.tableblock{border-width:1px 1px 0 0} +table.grid-cols>*>tr>.tableblock{border-width:0 1px 0 0} +table.grid-rows>thead>tr>.tableblock,table.grid-rows>tbody>tr>.tableblock{border-width:0 0 1px} +table.grid-rows>tfoot>tr>.tableblock{border-width:1px 0 0} +table.grid-all>*>tr>.tableblock:last-child,table.grid-cols>*>tr>.tableblock:last-child{border-right-width:0} +table.grid-all>tbody>tr:last-child>.tableblock,table.grid-all>thead:last-child>tr>.tableblock,table.grid-rows>tbody>tr:last-child>.tableblock,table.grid-rows>thead:last-child>tr>.tableblock{border-bottom-width:0} +table.frame-all{border-width:1px} +table.frame-sides{border-width:0 1px} +table.frame-topbot,table.frame-ends{border-width:1px 0} +table.stripes-all tr,table.stripes-odd tr:nth-of-type(odd){background:#f8f8f7} +table.stripes-none tr,table.stripes-odd tr:nth-of-type(even){background:none} +th.halign-left,td.halign-left{text-align:left} +th.halign-right,td.halign-right{text-align:right} +th.halign-center,td.halign-center{text-align:center} +th.valign-top,td.valign-top{vertical-align:top} +th.valign-bottom,td.valign-bottom{vertical-align:bottom} +th.valign-middle,td.valign-middle{vertical-align:middle} +table thead th,table tfoot th{font-weight:bold} +tbody tr th{display:table-cell;line-height:1.6;background:#f7f8f7} +tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold} +p.tableblock>code:only-child{background:none;padding:0} +p.tableblock{font-size:1em} +td>div.verse{white-space:pre} +ol{margin-left:1.75em} +ul li ol{margin-left:1.5em} +dl dd{margin-left:1.125em} +dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0} +ol>li p,ul>li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em} +ul.checklist,ul.none,ol.none,ul.no-bullet,ol.no-bullet,ol.unnumbered,ul.unstyled,ol.unstyled{list-style-type:none} +ul.no-bullet,ol.no-bullet,ol.unnumbered{margin-left:.625em} +ul.unstyled,ol.unstyled{margin-left:0} +ul.checklist{margin-left:.625em} +ul.checklist li>p:first-child>.fa-square-o:first-child,ul.checklist li>p:first-child>.fa-check-square-o:first-child{width:1.25em;font-size:.8em;position:relative;bottom:.125em} +ul.checklist li>p:first-child>input[type="checkbox"]:first-child{margin-right:.25em} +ul.inline{display:-ms-flexbox;display:-webkit-box;display:flex;-ms-flex-flow:row wrap;-webkit-flex-flow:row wrap;flex-flow:row wrap;list-style:none;margin:0 0 .625em -1.25em} +ul.inline>li{margin-left:1.25em} +.unstyled dl dt{font-weight:400;font-style:normal} +ol.arabic{list-style-type:decimal} +ol.decimal{list-style-type:decimal-leading-zero} +ol.loweralpha{list-style-type:lower-alpha} +ol.upperalpha{list-style-type:upper-alpha} +ol.lowerroman{list-style-type:lower-roman} +ol.upperroman{list-style-type:upper-roman} +ol.lowergreek{list-style-type:lower-greek} +.hdlist>table,.colist>table{border:0;background:none} +.hdlist>table>tbody>tr,.colist>table>tbody>tr{background:none} +td.hdlist1,td.hdlist2{vertical-align:top;padding:0 .625em} +td.hdlist1{font-weight:bold;padding-bottom:1.25em} +.literalblock+.colist,.listingblock+.colist{margin-top:-.5em} +.colist td:not([class]):first-child{padding:.4em .75em 0;line-height:1;vertical-align:top} +.colist td:not([class]):first-child img{max-width:none} +.colist td:not([class]):last-child{padding:.25em 0} +.thumb,.th{line-height:0;display:inline-block;border:solid 4px #fff;-webkit-box-shadow:0 0 0 1px #ddd;box-shadow:0 0 0 1px #ddd} +.imageblock.left{margin:.25em .625em 1.25em 0} +.imageblock.right{margin:.25em 0 1.25em .625em} +.imageblock>.title{margin-bottom:0} +.imageblock.thumb,.imageblock.th{border-width:6px} +.imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em} +.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0} +.image.left{margin-right:.625em} +.image.right{margin-left:.625em} +a.image{text-decoration:none;display:inline-block} +a.image object{pointer-events:none} +sup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super} +sup.footnote a,sup.footnoteref a{text-decoration:none} +sup.footnote a:active,sup.footnoteref a:active{text-decoration:underline} +#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em} +#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em;border-width:1px 0 0} +#footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;margin-bottom:.2em} +#footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none;margin-left:-1.05em} +#footnotes .footnote:last-of-type{margin-bottom:0} +#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0} +.gist .file-data>table{border:0;background:#fff;width:100%;margin-bottom:0} +.gist .file-data>table td.line-data{width:99%} +div.unbreakable{page-break-inside:avoid} +.big{font-size:larger} +.small{font-size:smaller} +.underline{text-decoration:underline} +.overline{text-decoration:overline} +.line-through{text-decoration:line-through} +.aqua{color:#00bfbf} +.aqua-background{background-color:#00fafa} +.black{color:#000} +.black-background{background-color:#000} +.blue{color:#0000bf} +.blue-background{background-color:#0000fa} +.fuchsia{color:#bf00bf} +.fuchsia-background{background-color:#fa00fa} +.gray{color:#606060} +.gray-background{background-color:#7d7d7d} +.green{color:#006000} +.green-background{background-color:#007d00} +.lime{color:#00bf00} +.lime-background{background-color:#00fa00} +.maroon{color:#600000} +.maroon-background{background-color:#7d0000} +.navy{color:#000060} +.navy-background{background-color:#00007d} +.olive{color:#606000} +.olive-background{background-color:#7d7d00} +.purple{color:#600060} +.purple-background{background-color:#7d007d} +.red{color:#bf0000} +.red-background{background-color:#fa0000} +.silver{color:#909090} +.silver-background{background-color:#bcbcbc} +.teal{color:#006060} +.teal-background{background-color:#007d7d} +.white{color:#bfbfbf} +.white-background{background-color:#fafafa} +.yellow{color:#bfbf00} +.yellow-background{background-color:#fafa00} +span.icon>.fa{cursor:default} +a span.icon>.fa{cursor:inherit} +.admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default} +.admonitionblock td.icon .icon-note::before{content:"\f05a";color:#19407c} +.admonitionblock td.icon .icon-tip::before{content:"\f0eb";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111} +.admonitionblock td.icon .icon-warning::before{content:"\f071";color:#bf6900} +.admonitionblock td.icon .icon-caution::before{content:"\f06d";color:#bf3400} +.admonitionblock td.icon .icon-important::before{content:"\f06a";color:#bf0000} +.conum[data-value]{display:inline-block;color:#fff!important;background-color:rgba(0,0,0,.8);-webkit-border-radius:100px;border-radius:100px;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold} +.conum[data-value] *{color:#fff!important} +.conum[data-value]+b{display:none} +.conum[data-value]::after{content:attr(data-value)} +pre .conum[data-value]{position:relative;top:-.125em} +b.conum *{color:inherit!important} +.conum:not([data-value]):empty{display:none} +dt,th.tableblock,td.content,div.footnote{text-rendering:optimizeLegibility} +h1,h2,p,td.content,span.alt{letter-spacing:-.01em} +p strong,td.content strong,div.footnote strong{letter-spacing:-.005em} +p,blockquote,dt,td.content,span.alt{font-size:1.0625rem} +p{margin-bottom:1.25rem} +.sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em} +.exampleblock>.content{background-color:#fffef7;border-color:#e0e0dc;-webkit-box-shadow:0 1px 4px #e0e0dc;box-shadow:0 1px 4px #e0e0dc} +.print-only{display:none!important} +@page{margin:1.25cm .75cm} +@media print{*{-webkit-box-shadow:none!important;box-shadow:none!important;text-shadow:none!important} +html{font-size:80%} +a{color:inherit!important;text-decoration:underline!important} +a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important} +a[href^="http:"]:not(.bare)::after,a[href^="https:"]:not(.bare)::after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em} +abbr[title]::after{content:" (" attr(title) ")"} +pre,blockquote,tr,img,object,svg{page-break-inside:avoid} +thead{display:table-header-group} +svg{max-width:100%} +p,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3} +h2,h3,#toctitle,.sidebarblock>.content>.title{page-break-after:avoid} +#toc,.sidebarblock,.exampleblock>.content{background:none!important} +#toc{border-bottom:1px solid #dddddf!important;padding-bottom:0!important} +body.book #header{text-align:center} +body.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em} +body.book #header .details{border:0!important;display:block;padding:0!important} +body.book #header .details span:first-child{margin-left:0!important} +body.book #header .details br{display:block} +body.book #header .details br+span::before{content:none!important} +body.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important} +body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-break-before:always} +.listingblock code[data-lang]::before{display:block} +#footer{padding:0 .9375em} +.hide-on-print{display:none!important} +.print-only{display:block!important} +.hide-for-print{display:none!important} +.show-for-print{display:inherit!important}} +@media print,amzn-kf8{#header>h1:first-child{margin-top:1.25rem} +.sect1{padding:0!important} +.sect1+.sect1{border:0} +#footer{background:none} +#footer-text{color:rgba(0,0,0,.6);font-size:.9em}} +@media amzn-kf8{#header,#content,#footnotes,#footer{padding:0}} + +/* Zajo's customizations applied on top of the standard asciidoctor css above */ +h1{font-size:4em} +h2{font-size:1.74em} +h3,#toctitle,.sidebarblock>.content>.title{font-size:1.5em} +h4{font-size:1.2em} +h5{font-size:1em} +h6{font-size:1em} +#toc {text-align:left} +#toc ul code{font-size:111%} +#toc a:hover code {color:#00cc99} +a:focus{outline:0} +.colist td{color:rgba(255,255,255,.67)} +body{text-align:left; background:#202020;color:rgba(255,255,255,.67);padding:0;margin:0;font-family:"Istok Web","DejaVu Serif",serif;font-weight:400;font-style:normal;line-height:1;position:relative;cursor:auto;tab-size:4;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased} +.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#a0a0a0;font-weight:400;margin-top:0;margin-bottom:.25em} +.literalblock pre,.listingblock pre:not(.highlight),.listingblock pre[class="highlight"],.listingblock pre[class^="highlight "],.listingblock pre.CodeRay,.listingblock pre.prettyprint{background:#101010} +table{background:#202020;margin-bottom:1.25em;border:solid 1px #dedede} +table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(255,255,255,.67)} +table tr.even,table tr.alt,table tr:nth-of-type(even){background:#202020} +table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{color:rgba(255,255,255,.67)} +th{background-color:#404040} +a{color:#FFFFFF;text-decoration:underline;line-height:inherit} +a:hover{color:#00cc99} +a:focus{color:#FFFFFF} +h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Quicksand","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#00cc99;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.4em} +code{font-family:"Anonymous Pro","DejaVu Sans Mono",monospace;font-weight:400;color:black} +*:not(pre)>code{font-size:1.0em;font-style:normal!important;letter-spacing:0;padding:0 0;word-spacing:-.15em;background-color:transparent;-webkit-border-radius:0;border-radius:0;line-height:1.45;text-rendering:optimizeLegibility;word-wrap:break-word;color:white} +pre,pre>code{line-height:1.45;color:rgba(255,255,255,.67);font-family:"Anonymous Pro","DejaVu Sans Mono",monospace;font-weight:400;text-rendering:optimizeLegibility;font-size:1.05em;background-color:#101010} +a:not(pre)>code:hover {color:#00cc99} +kbd{font-family:"Anonymous Pro","DejaVu Sans Mono",monospace;display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background-color:#f7f7f7;border:1px solid #ccc;-webkit-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em white inset;box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em #fff inset;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap} +h1 code{color:#00cc99; font-size:113%} +h2 code{color:#00cc99; font-size:113%} +h3 code{color:#00cc99; font-size:113%} +h4 code{color:#00cc99; font-size:113%} +h5 code{color:#00cc99; font-size:113%} +#header>h1:first-child{font-family:"Poiret One";color:#00cc99;margin-top:2.25rem;margin-bottom:0;letter-spacing:-.07em} +#author{color:#a366ff} +#toc ul{font-family:"Quicksand","DejaVu Sans",sans-serif;list-style-type:none} +#toc a:hover{color:#00cc99} +#toc.toc2{background-color:#404040} +.admonitionblock td.icon .icon-note::before{content:"\f05a";color:#00cc99} +.admonitionblock td.icon .icon-tip::before{content:"\f0eb";color:#00cc99;text-shadow:none} +.admonitionblock td.icon .icon-warning::before{content:"\f071";color:#a366ff} +.admonitionblock td.icon .icon-caution::before{content:"\f06d";color:#a366ff} +.admonitionblock td.icon .icon-important::before{content:"\f06a";color:#a366ff} +.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #dddddf;color:rgba(255,255,255,.67)} +.conum[data-value]{display:inline-block;color:black!important;background-color:#d9d9d9;-webkit-border-radius:100px;border-radius:100px;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold} +.exampleblock>.content{background-color:#404040;border-color:#e0e0dc;-webkit-box-shadow:0 1px 4px #e0e0dc;box-shadow:0 1px 4px #e0e0dc} +.quoteblock {background-color:#404040} +.quoteblock blockquote,.quoteblock p{color:rgba(255,255,255,.67);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify;background-color:#404040} +.quoteblock blockquote::before{margin-left:-.8em;color:#00cc99} +.quoteblock blockquote{font-family:"Istok Web","DejaVu Serif"; font-size:1.0625rem; padding:0.5em} +.quoteblock .attribution{padding-top:.75ex;margin-top:0;margin-right:0;padding-right:.5ex;text-align:right;background-color:#202020} +.text-right{margin-top:-1em} diff --git a/doc/zajo-light.css b/doc/zajo-light.css new file mode 100644 index 0000000..89a5cda --- /dev/null +++ b/doc/zajo-light.css @@ -0,0 +1,468 @@ +/* Asciidoctor default stylesheet | MIT License | http://asciidoctor.org */ +/* Uncomment @import statement below to use as custom stylesheet */ +/*@import "https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700";*/ + +/* Zajo's custom font import. The rest of the customizations are at the bottom of this css file, which is otherwise kept unchanged */ +@import "https://fonts.googleapis.com/css?family=Anonymous+Pro|Istok+Web|Quicksand|Poiret+One"; + +article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block} +audio,canvas,video{display:inline-block} +audio:not([controls]){display:none;height:0} +script{display:none!important} +html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%} +a{background:transparent} +a:focus{outline:thin dotted} +a:active,a:hover{outline:0} +h1{font-size:2em;margin:.67em 0} +abbr[title]{border-bottom:1px dotted} +b,strong{font-weight:bold} +dfn{font-style:italic} +hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0} +mark{background:#ff0;color:#000} +code,kbd,pre,samp{font-family:monospace;font-size:1em} +pre{white-space:pre-wrap} +q{quotes:"\201C" "\201D" "\2018" "\2019"} +small{font-size:80%} +sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline} +sup{top:-.5em} +sub{bottom:-.25em} +img{border:0} +svg:not(:root){overflow:hidden} +figure{margin:0} +fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em} +legend{border:0;padding:0} +button,input,select,textarea{font-family:inherit;font-size:100%;margin:0} +button,input{line-height:normal} +button,select{text-transform:none} +button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer} +button[disabled],html input[disabled]{cursor:default} +input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0} +button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0} +textarea{overflow:auto;vertical-align:top} +table{border-collapse:collapse;border-spacing:0} +*,*::before,*::after{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box} +html,body{font-size:100%} +body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;font-weight:400;font-style:normal;line-height:1;position:relative;cursor:auto;tab-size:4;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased} +a:hover{cursor:pointer} +img,object,embed{max-width:100%;height:auto} +object,embed{height:100%} +img{-ms-interpolation-mode:bicubic} +.left{float:left!important} +.right{float:right!important} +.text-left{text-align:left!important} +.text-right{text-align:right!important} +.text-center{text-align:center!important} +.text-justify{text-align:justify!important} +.hide{display:none} +img,object,svg{display:inline-block;vertical-align:middle} +textarea{height:auto;min-height:50px} +select{width:100%} +.center{margin-left:auto;margin-right:auto} +.stretch{width:100%} +.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em} +div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0;direction:ltr} +a{color:#2156a5;text-decoration:underline;line-height:inherit} +a:hover,a:focus{color:#1d4b8f} +a img{border:none} +p{font-family:inherit;font-weight:400;font-size:1em;line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility} +p aside{font-size:.875em;line-height:1.35;font-style:italic} +h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em} +h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0} +h1{font-size:2.125em} +h2{font-size:1.6875em} +h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em} +h4,h5{font-size:1.125em} +h6{font-size:1em} +hr{border:solid #dddddf;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em;height:0} +em,i{font-style:italic;line-height:inherit} +strong,b{font-weight:bold;line-height:inherit} +small{font-size:60%;line-height:inherit} +code{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;color:rgba(0,0,0,.9)} +ul,ol,dl{font-size:1em;line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit} +ul,ol{margin-left:1.5em} +ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0;font-size:1em} +ul.square li ul,ul.circle li ul,ul.disc li ul{list-style:inherit} +ul.square{list-style-type:square} +ul.circle{list-style-type:circle} +ul.disc{list-style-type:disc} +ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0} +dl dt{margin-bottom:.3125em;font-weight:bold} +dl dd{margin-bottom:1.25em} +abbr,acronym{text-transform:uppercase;font-size:90%;color:rgba(0,0,0,.8);border-bottom:1px dotted #ddd;cursor:help} +abbr{text-transform:none} +blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd} +blockquote cite{display:block;font-size:.9375em;color:rgba(0,0,0,.6)} +blockquote cite::before{content:"\2014 \0020"} +blockquote cite a,blockquote cite a:visited{color:rgba(0,0,0,.6)} +blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)} +@media screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2} +h1{font-size:2.75em} +h2{font-size:2.3125em} +h3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em} +h4{font-size:1.4375em}} +table{background:#fff;margin-bottom:1.25em;border:solid 1px #dedede} +table thead,table tfoot{background:#f7f8f7} +table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left} +table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)} +table tr.even,table tr.alt,table tr:nth-of-type(even){background:#f8f8f7} +table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{display:table-cell;line-height:1.6} +h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2;word-spacing:-.05em} +h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400} +.clearfix::before,.clearfix::after,.float-group::before,.float-group::after{content:" ";display:table} +.clearfix::after,.float-group::after{clear:both} +*:not(pre)>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background-color:#f7f7f8;-webkit-border-radius:4px;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed;word-wrap:break-word} +*:not(pre)>code.nobreak{word-wrap:normal} +*:not(pre)>code.nowrap{white-space:nowrap} +pre,pre>code{line-height:1.45;color:rgba(0,0,0,.9);font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;text-rendering:optimizeSpeed} +em em{font-style:normal} +strong strong{font-weight:400} +.keyseq{color:rgba(51,51,51,.8)} +kbd{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background-color:#f7f7f7;border:1px solid #ccc;-webkit-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em white inset;box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em #fff inset;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap} +.keyseq kbd:first-child{margin-left:0} +.keyseq kbd:last-child{margin-right:0} +.menuseq,.menuref{color:#000} +.menuseq b:not(.caret),.menuref{font-weight:inherit} +.menuseq{word-spacing:-.02em} +.menuseq b.caret{font-size:1.25em;line-height:.8} +.menuseq i.caret{font-weight:bold;text-align:center;width:.45em} +b.button::before,b.button::after{position:relative;top:-1px;font-weight:400} +b.button::before{content:"[";padding:0 3px 0 2px} +b.button::after{content:"]";padding:0 2px 0 3px} +p a>code:hover{color:rgba(0,0,0,.9)} +#header,#content,#footnotes,#footer{width:100%;margin-left:auto;margin-right:auto;margin-top:0;margin-bottom:0;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em} +#header::before,#header::after,#content::before,#content::after,#footnotes::before,#footnotes::after,#footer::before,#footer::after{content:" ";display:table} +#header::after,#content::after,#footnotes::after,#footer::after{clear:both} +#content{margin-top:1.25em} +#content::before{content:none} +#header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0} +#header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #dddddf} +#header>h1:only-child,body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #dddddf;padding-bottom:8px} +#header .details{border-bottom:1px solid #dddddf;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:-ms-flexbox;display:-webkit-flex;display:flex;-ms-flex-flow:row wrap;-webkit-flex-flow:row wrap;flex-flow:row wrap} +#header .details span:first-child{margin-left:-.125em} +#header .details span.email a{color:rgba(0,0,0,.85)} +#header .details br{display:none} +#header .details br+span::before{content:"\00a0\2013\00a0"} +#header .details br+span.author::before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)} +#header .details br+span#revremark::before{content:"\00a0|\00a0"} +#header #revnumber{text-transform:capitalize} +#header #revnumber::after{content:"\00a0"} +#content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #dddddf;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem} +#toc{border-bottom:1px solid #e7e7e9;padding-bottom:.5em} +#toc>ul{margin-left:.125em} +#toc ul.sectlevel0>li>a{font-style:italic} +#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0} +#toc ul{font-family:"Open Sans","DejaVu Sans",sans-serif;list-style-type:none} +#toc li{line-height:1.3334;margin-top:.3334em} +#toc a{text-decoration:none} +#toc a:active{text-decoration:underline} +#toctitle{color:#7a2518;font-size:1.2em} +@media screen and (min-width:768px){#toctitle{font-size:1.375em} +body.toc2{padding-left:15em;padding-right:0} +#toc.toc2{margin-top:0!important;background-color:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #e7e7e9;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto} +#toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em} +#toc.toc2>ul{font-size:.9em;margin-bottom:0} +#toc.toc2 ul ul{margin-left:0;padding-left:1em} +#toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em} +body.toc2.toc-right{padding-left:0;padding-right:15em} +body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #e7e7e9;left:auto;right:0}} +@media screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0} +#toc.toc2{width:20em} +#toc.toc2 #toctitle{font-size:1.375em} +#toc.toc2>ul{font-size:.95em} +#toc.toc2 ul ul{padding-left:1.25em} +body.toc2.toc-right{padding-left:0;padding-right:20em}} +#content #toc{border-style:solid;border-width:1px;border-color:#e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;-webkit-border-radius:4px;border-radius:4px} +#content #toc>:first-child{margin-top:0} +#content #toc>:last-child{margin-bottom:0} +#footer{max-width:100%;background-color:rgba(0,0,0,.8);padding:1.25em} +#footer-text{color:rgba(255,255,255,.8);line-height:1.44} +#content{margin-bottom:.625em} +.sect1{padding-bottom:.625em} +@media screen and (min-width:768px){#content{margin-bottom:1.25em} +.sect1{padding-bottom:1.25em}} +.sect1:last-child{padding-bottom:0} +.sect1+.sect1{border-top:1px solid #e7e7e9} +#content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400} +#content h1>a.anchor::before,h2>a.anchor::before,h3>a.anchor::before,#toctitle>a.anchor::before,.sidebarblock>.content>.title>a.anchor::before,h4>a.anchor::before,h5>a.anchor::before,h6>a.anchor::before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em} +#content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible} +#content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none} +#content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221} +.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em} +.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:"Noto Serif","DejaVu Serif",serif;font-size:1rem;font-style:italic} +table.tableblock.fit-content>caption.title{white-space:nowrap;width:0} +.paragraph.lead>p,#preamble>.sectionbody>[class="paragraph"]:first-of-type p{font-size:1.21875em;line-height:1.6;color:rgba(0,0,0,.85)} +table.tableblock #preamble>.sectionbody>[class="paragraph"]:first-of-type p{font-size:inherit} +.admonitionblock>table{border-collapse:separate;border:0;background:none;width:100%} +.admonitionblock>table td.icon{text-align:center;width:80px} +.admonitionblock>table td.icon img{max-width:none} +.admonitionblock>table td.icon .title{font-weight:bold;font-family:"Open Sans","DejaVu Sans",sans-serif;text-transform:uppercase} +.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #dddddf;color:rgba(0,0,0,.6)} +.admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0} +.exampleblock>.content{border-style:solid;border-width:1px;border-color:#e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;-webkit-border-radius:4px;border-radius:4px} +.exampleblock>.content>:first-child{margin-top:0} +.exampleblock>.content>:last-child{margin-bottom:0} +.sidebarblock{border-style:solid;border-width:1px;border-color:#e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;-webkit-border-radius:4px;border-radius:4px} +.sidebarblock>:first-child{margin-top:0} +.sidebarblock>:last-child{margin-bottom:0} +.sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center} +.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0} +.literalblock pre,.listingblock pre:not(.highlight),.listingblock pre[class="highlight"],.listingblock pre[class^="highlight "],.listingblock pre.CodeRay,.listingblock pre.prettyprint{background:#f7f7f8} +.sidebarblock .literalblock pre,.sidebarblock .listingblock pre:not(.highlight),.sidebarblock .listingblock pre[class="highlight"],.sidebarblock .listingblock pre[class^="highlight "],.sidebarblock .listingblock pre.CodeRay,.sidebarblock .listingblock pre.prettyprint{background:#f2f1f1} +.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{-webkit-border-radius:4px;border-radius:4px;word-wrap:break-word;overflow-x:auto;padding:1em;font-size:.8125em} +@media screen and (min-width:768px){.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{font-size:.90625em}} +@media screen and (min-width:1280px){.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{font-size:1em}} +.literalblock pre.nowrap,.literalblock pre.nowrap pre,.listingblock pre.nowrap,.listingblock pre.nowrap pre{white-space:pre;word-wrap:normal} +.literalblock.output pre{color:#f7f7f8;background-color:rgba(0,0,0,.9)} +.listingblock pre.highlightjs{padding:0} +.listingblock pre.highlightjs>code{padding:1em;-webkit-border-radius:4px;border-radius:4px} +.listingblock pre.prettyprint{border-width:0} +.listingblock>.content{position:relative} +.listingblock code[data-lang]::before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:#999} +.listingblock:hover code[data-lang]::before{display:block} +.listingblock.terminal pre .command::before{content:attr(data-prompt);padding-right:.5em;color:#999} +.listingblock.terminal pre .command:not([data-prompt])::before{content:"$"} +table.pyhltable{border-collapse:separate;border:0;margin-bottom:0;background:none} +table.pyhltable td{vertical-align:top;padding-top:0;padding-bottom:0;line-height:1.45} +table.pyhltable td.code{padding-left:.75em;padding-right:0} +pre.pygments .lineno,table.pyhltable td:not(.code){color:#999;padding-left:0;padding-right:.5em;border-right:1px solid #dddddf} +pre.pygments .lineno{display:inline-block;margin-right:.25em} +table.pyhltable .linenodiv{background:none!important;padding-right:0!important} +.quoteblock{margin:0 1em 1.25em 1.5em;display:table} +.quoteblock>.title{margin-left:-1.5em;margin-bottom:.75em} +.quoteblock blockquote,.quoteblock p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify} +.quoteblock blockquote{margin:0;padding:0;border:0} +.quoteblock blockquote::before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)} +.quoteblock blockquote>.paragraph:last-child p{margin-bottom:0} +.quoteblock .attribution{margin-top:.75em;margin-right:.5ex;text-align:right} +.verseblock{margin:0 1em 1.25em} +.verseblock pre{font-family:"Open Sans","DejaVu Sans",sans;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility} +.verseblock pre strong{font-weight:400} +.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex} +.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic} +.quoteblock .attribution br,.verseblock .attribution br{display:none} +.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.025em;color:rgba(0,0,0,.6)} +.quoteblock.abstract blockquote::before,.quoteblock.excerpt blockquote::before,.quoteblock .quoteblock blockquote::before{display:none} +.quoteblock.abstract blockquote,.quoteblock.abstract p,.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{line-height:1.6;word-spacing:0} +.quoteblock.abstract{margin:0 1em 1.25em;display:block} +.quoteblock.abstract>.title{margin:0 0 .375em;font-size:1.15em;text-align:center} +.quoteblock.excerpt,.quoteblock .quoteblock{margin:0 0 1.25em;padding:0 0 .25em 1em;border-left:.25em solid #dddddf} +.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{color:inherit;font-size:1.0625rem} +.quoteblock.excerpt .attribution,.quoteblock .quoteblock .attribution{color:inherit;text-align:left;margin-right:0} +table.tableblock{max-width:100%;border-collapse:separate} +p.tableblock:last-child{margin-bottom:0} +td.tableblock>.content{margin-bottom:-1.25em} +table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede} +table.grid-all>thead>tr>.tableblock,table.grid-all>tbody>tr>.tableblock{border-width:0 1px 1px 0} +table.grid-all>tfoot>tr>.tableblock{border-width:1px 1px 0 0} +table.grid-cols>*>tr>.tableblock{border-width:0 1px 0 0} +table.grid-rows>thead>tr>.tableblock,table.grid-rows>tbody>tr>.tableblock{border-width:0 0 1px} +table.grid-rows>tfoot>tr>.tableblock{border-width:1px 0 0} +table.grid-all>*>tr>.tableblock:last-child,table.grid-cols>*>tr>.tableblock:last-child{border-right-width:0} +table.grid-all>tbody>tr:last-child>.tableblock,table.grid-all>thead:last-child>tr>.tableblock,table.grid-rows>tbody>tr:last-child>.tableblock,table.grid-rows>thead:last-child>tr>.tableblock{border-bottom-width:0} +table.frame-all{border-width:1px} +table.frame-sides{border-width:0 1px} +table.frame-topbot,table.frame-ends{border-width:1px 0} +table.stripes-all tr,table.stripes-odd tr:nth-of-type(odd){background:#f8f8f7} +table.stripes-none tr,table.stripes-odd tr:nth-of-type(even){background:none} +th.halign-left,td.halign-left{text-align:left} +th.halign-right,td.halign-right{text-align:right} +th.halign-center,td.halign-center{text-align:center} +th.valign-top,td.valign-top{vertical-align:top} +th.valign-bottom,td.valign-bottom{vertical-align:bottom} +th.valign-middle,td.valign-middle{vertical-align:middle} +table thead th,table tfoot th{font-weight:bold} +tbody tr th{display:table-cell;line-height:1.6;background:#f7f8f7} +tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold} +p.tableblock>code:only-child{background:none;padding:0} +p.tableblock{font-size:1em} +td>div.verse{white-space:pre} +ol{margin-left:1.75em} +ul li ol{margin-left:1.5em} +dl dd{margin-left:1.125em} +dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0} +ol>li p,ul>li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em} +ul.checklist,ul.none,ol.none,ul.no-bullet,ol.no-bullet,ol.unnumbered,ul.unstyled,ol.unstyled{list-style-type:none} +ul.no-bullet,ol.no-bullet,ol.unnumbered{margin-left:.625em} +ul.unstyled,ol.unstyled{margin-left:0} +ul.checklist{margin-left:.625em} +ul.checklist li>p:first-child>.fa-square-o:first-child,ul.checklist li>p:first-child>.fa-check-square-o:first-child{width:1.25em;font-size:.8em;position:relative;bottom:.125em} +ul.checklist li>p:first-child>input[type="checkbox"]:first-child{margin-right:.25em} +ul.inline{display:-ms-flexbox;display:-webkit-box;display:flex;-ms-flex-flow:row wrap;-webkit-flex-flow:row wrap;flex-flow:row wrap;list-style:none;margin:0 0 .625em -1.25em} +ul.inline>li{margin-left:1.25em} +.unstyled dl dt{font-weight:400;font-style:normal} +ol.arabic{list-style-type:decimal} +ol.decimal{list-style-type:decimal-leading-zero} +ol.loweralpha{list-style-type:lower-alpha} +ol.upperalpha{list-style-type:upper-alpha} +ol.lowerroman{list-style-type:lower-roman} +ol.upperroman{list-style-type:upper-roman} +ol.lowergreek{list-style-type:lower-greek} +.hdlist>table,.colist>table{border:0;background:none} +.hdlist>table>tbody>tr,.colist>table>tbody>tr{background:none} +td.hdlist1,td.hdlist2{vertical-align:top;padding:0 .625em} +td.hdlist1{font-weight:bold;padding-bottom:1.25em} +.literalblock+.colist,.listingblock+.colist{margin-top:-.5em} +.colist td:not([class]):first-child{padding:.4em .75em 0;line-height:1;vertical-align:top} +.colist td:not([class]):first-child img{max-width:none} +.colist td:not([class]):last-child{padding:.25em 0} +.thumb,.th{line-height:0;display:inline-block;border:solid 4px #fff;-webkit-box-shadow:0 0 0 1px #ddd;box-shadow:0 0 0 1px #ddd} +.imageblock.left{margin:.25em .625em 1.25em 0} +.imageblock.right{margin:.25em 0 1.25em .625em} +.imageblock>.title{margin-bottom:0} +.imageblock.thumb,.imageblock.th{border-width:6px} +.imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em} +.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0} +.image.left{margin-right:.625em} +.image.right{margin-left:.625em} +a.image{text-decoration:none;display:inline-block} +a.image object{pointer-events:none} +sup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super} +sup.footnote a,sup.footnoteref a{text-decoration:none} +sup.footnote a:active,sup.footnoteref a:active{text-decoration:underline} +#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em} +#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em;border-width:1px 0 0} +#footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;margin-bottom:.2em} +#footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none;margin-left:-1.05em} +#footnotes .footnote:last-of-type{margin-bottom:0} +#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0} +.gist .file-data>table{border:0;background:#fff;width:100%;margin-bottom:0} +.gist .file-data>table td.line-data{width:99%} +div.unbreakable{page-break-inside:avoid} +.big{font-size:larger} +.small{font-size:smaller} +.underline{text-decoration:underline} +.overline{text-decoration:overline} +.line-through{text-decoration:line-through} +.aqua{color:#00bfbf} +.aqua-background{background-color:#00fafa} +.black{color:#000} +.black-background{background-color:#000} +.blue{color:#0000bf} +.blue-background{background-color:#0000fa} +.fuchsia{color:#bf00bf} +.fuchsia-background{background-color:#fa00fa} +.gray{color:#606060} +.gray-background{background-color:#7d7d7d} +.green{color:#006000} +.green-background{background-color:#007d00} +.lime{color:#00bf00} +.lime-background{background-color:#00fa00} +.maroon{color:#600000} +.maroon-background{background-color:#7d0000} +.navy{color:#000060} +.navy-background{background-color:#00007d} +.olive{color:#606000} +.olive-background{background-color:#7d7d00} +.purple{color:#600060} +.purple-background{background-color:#7d007d} +.red{color:#bf0000} +.red-background{background-color:#fa0000} +.silver{color:#909090} +.silver-background{background-color:#bcbcbc} +.teal{color:#006060} +.teal-background{background-color:#007d7d} +.white{color:#bfbfbf} +.white-background{background-color:#fafafa} +.yellow{color:#bfbf00} +.yellow-background{background-color:#fafa00} +span.icon>.fa{cursor:default} +a span.icon>.fa{cursor:inherit} +.admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default} +.admonitionblock td.icon .icon-note::before{content:"\f05a";color:#19407c} +.admonitionblock td.icon .icon-tip::before{content:"\f0eb";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111} +.admonitionblock td.icon .icon-warning::before{content:"\f071";color:#bf6900} +.admonitionblock td.icon .icon-caution::before{content:"\f06d";color:#bf3400} +.admonitionblock td.icon .icon-important::before{content:"\f06a";color:#bf0000} +.conum[data-value]{display:inline-block;color:#fff!important;background-color:rgba(0,0,0,.8);-webkit-border-radius:100px;border-radius:100px;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold} +.conum[data-value] *{color:#fff!important} +.conum[data-value]+b{display:none} +.conum[data-value]::after{content:attr(data-value)} +pre .conum[data-value]{position:relative;top:-.125em} +b.conum *{color:inherit!important} +.conum:not([data-value]):empty{display:none} +dt,th.tableblock,td.content,div.footnote{text-rendering:optimizeLegibility} +h1,h2,p,td.content,span.alt{letter-spacing:-.01em} +p strong,td.content strong,div.footnote strong{letter-spacing:-.005em} +p,blockquote,dt,td.content,span.alt{font-size:1.0625rem} +p{margin-bottom:1.25rem} +.sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em} +.exampleblock>.content{background-color:#fffef7;border-color:#e0e0dc;-webkit-box-shadow:0 1px 4px #e0e0dc;box-shadow:0 1px 4px #e0e0dc} +.print-only{display:none!important} +@page{margin:1.25cm .75cm} +@media print{*{-webkit-box-shadow:none!important;box-shadow:none!important;text-shadow:none!important} +html{font-size:80%} +a{color:inherit!important;text-decoration:underline!important} +a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important} +a[href^="http:"]:not(.bare)::after,a[href^="https:"]:not(.bare)::after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em} +abbr[title]::after{content:" (" attr(title) ")"} +pre,blockquote,tr,img,object,svg{page-break-inside:avoid} +thead{display:table-header-group} +svg{max-width:100%} +p,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3} +h2,h3,#toctitle,.sidebarblock>.content>.title{page-break-after:avoid} +#toc,.sidebarblock,.exampleblock>.content{background:none!important} +#toc{border-bottom:1px solid #dddddf!important;padding-bottom:0!important} +body.book #header{text-align:center} +body.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em} +body.book #header .details{border:0!important;display:block;padding:0!important} +body.book #header .details span:first-child{margin-left:0!important} +body.book #header .details br{display:block} +body.book #header .details br+span::before{content:none!important} +body.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important} +body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-break-before:always} +.listingblock code[data-lang]::before{display:block} +#footer{padding:0 .9375em} +.hide-on-print{display:none!important} +.print-only{display:block!important} +.hide-for-print{display:none!important} +.show-for-print{display:inherit!important}} +@media print,amzn-kf8{#header>h1:first-child{margin-top:1.25rem} +.sect1{padding:0!important} +.sect1+.sect1{border:0} +#footer{background:none} +#footer-text{color:rgba(0,0,0,.6);font-size:.9em}} +@media amzn-kf8{#header,#content,#footnotes,#footer{padding:0}} + +/* Zajo's customizations applied on top of the standard asciidoctor css above */ +h1{font-size:4em} +h2{font-size:1.74em} +h3,#toctitle,.sidebarblock>.content>.title{font-size:1.5em} +h4{font-size:1.2em} +h5{font-size:1em} +h6{font-size:1em} +#toc {text-align:left} +#toc ul code{font-size:111%} +#toc a:hover code {color:#4101a7} +a:focus{outline:0} +.colist td{color:rgba(0,0,0,.67)} +body{text-align:left; background:#fff;color:rgba(0,0,0,.67);padding:0;margin:0;font-family:"Istok Web","DejaVu Serif",serif;font-weight:400;font-style:normal;line-height:1;position:relative;cursor:auto;tab-size:4;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased} +.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#a0a0a0;font-weight:400;margin-top:0;margin-bottom:.25em} +a{color:#000000;text-decoration:underline;line-height:inherit} +a:hover{color:#4101a7} +a:focus{color:#000000} +h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Quicksand","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#4101a7;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.4em} +code{font-family:"Anonymous Pro","DejaVu Sans Mono",monospace;font-weight:400;color:black} +*:not(pre)>code{font-size:1.0em;font-style:normal!important;letter-spacing:0;padding:0 0;word-spacing:-.15em;background-color:transparent;-webkit-border-radius:0;border-radius:0;line-height:1.45;text-rendering:optimizeLegibility;word-wrap:break-word} +pre,pre>code{line-height:1.45;color:rgba(0,0,0,.9);font-family:"Anonymous Pro","DejaVu Sans Mono",monospace;font-weight:400;text-rendering:optimizeLegibility;font-size:1.05em;background-color:#f7f8f7} +a:not(pre)>code:hover {color:#4101a7} +kbd{font-family:"Anonymous Pro","DejaVu Sans Mono",monospace;display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background-color:#f7f7f7;border:1px solid #ccc;-webkit-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em white inset;box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em #fff inset;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap} +h1 code{color:#4101a7; font-size:113%} +h2 code{color:#4101a7; font-size:113%} +h3 code{color:#4101a7; font-size:113%} +h4 code{color:#4101a7; font-size:113%} +h5 code{color:#4101a7; font-size:113%} +#header>h1:first-child{font-family:"Poiret One";color:#ff5100;margin-top:2.25rem;margin-bottom:0;letter-spacing:-.07em} +#author{color: #4101a7;} +#toc ul{font-family:"Quicksand","DejaVu Sans",sans-serif;list-style-type:none} +#toc a:hover{color:#4101a7} +#toc.toc2{background-color:#f7f8f7} +.admonitionblock td.icon .icon-note::before{content:"\f05a";color:#606060} +.admonitionblock td.icon .icon-tip::before{content:"\f0eb";color:#606060;text-shadow:none} +.admonitionblock td.icon .icon-warning::before{content:"\f071";color:#ff5100} +.admonitionblock td.icon .icon-caution::before{content:"\f06d";color:#ff5100} +.admonitionblock td.icon .icon-important::before{content:"\f06a";color:#ff5100} +.conum[data-value]{display:inline-block;color:#fff!important;background-color:#606060;-webkit-border-radius:100px;border-radius:100px;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold} +.exampleblock>.content{background-color:#ffffff;border-color:#e0e0dc;-webkit-box-shadow:0 1px 4px #e0e0dc;box-shadow:0 1px 4px #e0e0dc} +.quoteblock blockquote::before{margin-left:-.8em;color:#4101a7} +.quoteblock blockquote{font-family:"Istok Web","DejaVu Serif"; font-size:1.0625rem; padding:0.5em} +.text-right{margin-top:-1em} diff --git a/docs b/docs new file mode 120000 index 0000000..ea241a2 --- /dev/null +++ b/docs @@ -0,0 +1 @@ +doc/html \ No newline at end of file diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..a0ed71d --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,106 @@ +# Copyright (c) 2018-2024 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) + +if (CMAKE_BUILD_TYPE MATCHES "Release") + if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-save-temps -masm=intel) + endif() +endif() + +add_executable(virtual_func virtual_func.cpp) +target_link_libraries(virtual_func Boost::openmethod) +add_test(NAME virtual_func COMMAND virtual_func) + +add_executable(ast ast.cpp) +target_link_libraries(ast Boost::openmethod) +add_test(NAME ast COMMAND ast) + +add_executable(ast_unique_ptr ast_unique_ptr.cpp) +target_link_libraries(ast_unique_ptr Boost::openmethod) +add_test(NAME ast_unique_ptr COMMAND ast_unique_ptr) + +add_executable(hello_world hello_world.cpp) +target_link_libraries(hello_world Boost::openmethod) +add_test(NAME hello_world COMMAND hello_world) + +add_executable(friendship_all friendship.cpp) +target_compile_definitions(friendship_all PRIVATE FRIEND_ALL) +target_link_libraries(friendship_all Boost::openmethod) +add_test(NAME friendship_add COMMAND friendship_all) + +add_executable(friendship friendship.cpp) +target_link_libraries(friendship Boost::openmethod) +add_test(NAME friendship COMMAND friendship) + +add_executable(friendship_across_namespaces_all friendship_across_namespaces.cpp) +target_compile_definitions(friendship_across_namespaces_all PRIVATE FRIEND_ALL) +target_link_libraries(friendship_across_namespaces_all Boost::openmethod) +add_test(NAME friendship_across_namespaces_all COMMAND friendship_across_namespaces_all) + +add_executable(friendship_across_namespaces friendship_across_namespaces.cpp) +target_link_libraries(friendship_across_namespaces Boost::openmethod) +add_test(NAME friendship_across_namespaces COMMAND friendship_across_namespaces) + +add_executable(virtual_ptr virtual_ptr.cpp) +target_link_libraries(virtual_ptr Boost::openmethod) +add_test(NAME virtual_ptr COMMAND virtual_ptr) + +add_executable(virtual_ virtual_.cpp) +target_link_libraries(virtual_ Boost::openmethod) +add_test(NAME virtual_ COMMAND virtual_) + +add_executable(core_api core_api.cpp) +target_link_libraries(core_api Boost::openmethod) +add_test(NAME core_api COMMAND core_api) + +add_executable(vectored_error_handler vectored_error_handler.cpp) +target_link_libraries(vectored_error_handler Boost::openmethod) +add_test(NAME vectored_error_handler COMMAND vectored_error_handler) + +add_executable(throw_error_handler throw_error_handler.cpp) +target_link_libraries(throw_error_handler Boost::openmethod) +add_test(NAME throw_error_handler COMMAND throw_error_handler) + +add_executable(custom_rtti custom_rtti.cpp) +target_link_libraries(custom_rtti Boost::openmethod) +if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set_target_properties(custom_rtti PROPERTIES COMPILE_FLAGS "-fno-rtti") +endif() +add_test(NAME custom_rtti COMMAND custom_rtti) + +add_executable(deferred_custom_rtti deferred_custom_rtti.cpp) +target_link_libraries(deferred_custom_rtti Boost::openmethod) +if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set_target_properties(deferred_custom_rtti PROPERTIES COMPILE_FLAGS "-fno-rtti") +endif() +add_test(NAME deferred_custom_rtti COMMAND deferred_custom_rtti) + +add_executable(slides slides.cpp) +target_link_libraries(slides Boost::openmethod) +add_test(NAME slides COMMAND slides) + +add_executable(synopsis synopsis.cpp) +target_link_libraries(synopsis Boost::openmethod) +add_test(NAME synopsis COMMAND synopsis) + +add_executable(matrix matrix.cpp) +target_link_libraries(matrix Boost::openmethod) +add_test(NAME matrix COMMAND matrix) + +add_executable(accept_no_visitors accept_no_visitors.cpp) +target_link_libraries(accept_no_visitors Boost::openmethod) +add_test(NAME accept_no_visitors COMMAND accept_no_visitors) + +add_executable(adventure adventure.cpp) +target_link_libraries(adventure Boost::openmethod) +add_test(NAME adventure COMMAND adventure) + +add_executable(next next.cpp) +target_link_libraries(next Boost::openmethod) +add_test(NAME next COMMAND next) + +add_executable(asteroids asteroids.cpp) +target_link_libraries(asteroids Boost::openmethod) +add_test(NAME asteroids COMMAND asteroids) diff --git a/examples/accept_no_visitors.cpp b/examples/accept_no_visitors.cpp new file mode 100644 index 0000000..27cf2d3 --- /dev/null +++ b/examples/accept_no_visitors.cpp @@ -0,0 +1,123 @@ +// Copyright (c) 2018-2025 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) + +#include +#include +#include + +#include +#include +#include + +using boost::openmethod::make_shared_virtual; +using boost::openmethod::shared_virtual_ptr; + +using std::cout; +using std::string; + +struct Node { + virtual ~Node() { + } +}; + +struct Plus : Node { + Plus( + shared_virtual_ptr left, + shared_virtual_ptr right) + : left(left), right(right) { + } + + shared_virtual_ptr left, right; +}; + +struct Times : Node { + Times( + shared_virtual_ptr left, + shared_virtual_ptr right) + : left(left), right(right) { + } + + shared_virtual_ptr left, right; +}; + +struct Integer : Node { + explicit Integer(int value) : value(value) { + } + int value; +}; + +// ============================================================================= +// add behavior to existing classes, without changing them + +BOOST_OPENMETHOD_CLASSES(Node, Plus, Times, Integer); + +// ----------------------------------------------------------------------------- +// evaluate + +BOOST_OPENMETHOD(value, (virtual_ptr), int); + +BOOST_OPENMETHOD_OVERRIDE(value, (virtual_ptr expr), int) { + return value(expr->left) + value(expr->right); +} + +BOOST_OPENMETHOD_OVERRIDE(value, (virtual_ptr expr), int) { + return value(expr->left) * value(expr->right); +} + +BOOST_OPENMETHOD_OVERRIDE(value, (virtual_ptr expr), int) { + return expr->value; +} + +// ----------------------------------------------------------------------------- +// render as Forth + +BOOST_OPENMETHOD(as_forth, (virtual_ptr), string); + +BOOST_OPENMETHOD_OVERRIDE(as_forth, (virtual_ptr expr), string) { + return as_forth(expr->left) + " " + as_forth(expr->right) + " +"; +} + +BOOST_OPENMETHOD_OVERRIDE(as_forth, (virtual_ptr expr), string) { + return as_forth(expr->left) + " " + as_forth(expr->right) + " *"; +} + +BOOST_OPENMETHOD_OVERRIDE(as_forth, (virtual_ptr expr), string) { + return std::to_string(expr->value); +} + +// ----------------------------------------------------------------------------- +// render as Lisp + +BOOST_OPENMETHOD(as_lisp, (virtual_ptr), string); + +BOOST_OPENMETHOD_OVERRIDE(as_lisp, (virtual_ptr expr), string) { + return "(plus " + as_lisp(expr->left) + " " + as_lisp(expr->right) + ")"; +} + +BOOST_OPENMETHOD_OVERRIDE(as_lisp, (virtual_ptr expr), string) { + return "(times " + as_lisp(expr->left) + " " + as_lisp(expr->right) + ")"; +} + +BOOST_OPENMETHOD_OVERRIDE(as_lisp, (virtual_ptr expr), string) { + return std::to_string(expr->value); +} + +// ----------------------------------------------------------------------------- + +int main() { + boost::openmethod::initialize(); + + shared_virtual_ptr expr = make_shared_virtual( + make_shared_virtual(2), + make_shared_virtual( + make_shared_virtual(3), make_shared_virtual(4))); + + cout << as_forth(expr) << " = " << as_lisp(expr) << " = " << value(expr) + << "\n"; + // error_output: + // 2 3 4 + * = (times 2 (plus 3 4)) = 14 + + return 0; +} diff --git a/examples/adventure.cpp b/examples/adventure.cpp new file mode 100644 index 0000000..9390c18 --- /dev/null +++ b/examples/adventure.cpp @@ -0,0 +1,128 @@ +// Copyright (c) 2018-2025 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) + +#include +#include +#include + +#include +#include + +struct Character { + virtual ~Character() { + } +}; + +struct Warrior : Character {}; + +struct Device { + virtual ~Device() { + } +}; + +struct Hands : Device {}; +struct Axe : Device {}; +struct Banana : Device {}; + +struct Creature { + virtual ~Creature() { + } +}; + +struct Dragon : Creature {}; +struct Bear : Creature {}; + +BOOST_OPENMETHOD_CLASSES( + Character, Warrior, Device, Hands, Axe, Banana, Creature, Dragon, Bear); + +BOOST_OPENMETHOD( + fight, (virtual_ptr, virtual_ptr, virtual_ptr), + std::string); + +BOOST_OPENMETHOD_OVERRIDE( + fight, + (virtual_ptr x, virtual_ptr y, virtual_ptr z), + std::string) { + return "are you insane?"; +} + +BOOST_OPENMETHOD_OVERRIDE( + fight, + (virtual_ptr x, virtual_ptr y, virtual_ptr z), + std::string) { + return "not agile enough to wield"; +} + +BOOST_OPENMETHOD_OVERRIDE( + fight, + (virtual_ptr x, virtual_ptr y, virtual_ptr z), + std::string) { + return "and cuts it into pieces"; +} + +BOOST_OPENMETHOD_OVERRIDE( + fight, (virtual_ptr x, virtual_ptr y, virtual_ptr z), + std::string) { + return "and dies a honorable death"; +} + +BOOST_OPENMETHOD_OVERRIDE( + fight, + (virtual_ptr x, virtual_ptr y, virtual_ptr z), + std::string) { + return "Congratulations! You have just vainquished a dragon with your bare " + "hands" + " (unbelievable, isn't it?)"; +} + +int main() { + boost::openmethod::initialize(); + + std::unique_ptr bob = std::make_unique(), + rambo = std::make_unique(); + + std::unique_ptr elliott = std::make_unique(), + paddington = std::make_unique(); + + std::unique_ptr hands = std::make_unique(), + axe = std::make_unique(), + chiquita = std::make_unique(); + + std::cout << "bob fights elliot with axe:\n" + << fight(*bob, *elliott, *axe) << "\n"; + // bob fights elliot with axe: + // not agile enough to wield + + std::cout << "rambo fights paddington with axe:\n" + << fight(*rambo, *paddington, *axe) << "\n"; + // rambo fights paddington with axe: + // and cuts it into pieces + + std::cout << "rambo fights paddington with banana:\n" + << fight(*rambo, *paddington, *chiquita) << "\n"; + // rambo fights paddington with banana: + // are you insane? + + std::cout << "rambo fights elliott with axe:\n" + << fight(*rambo, *elliott, *axe) << "\n"; + // rambo fights elliott with axe: + // and dies a honorable death + + std::cout << "bob fights elliot with hands:\n" + << fight(*bob, *elliott, *hands) << "\n"; + // bob fights elliot with hands: Congratulations! You have just vainquished + // a dragon with your bare hands (unbelievable, isn't it?) + + std::cout << "rambo fights elliot with hands:\n" + << fight(*rambo, *elliott, *hands) << "\n"; + // rambo fights elliot with hands: + // you just killed a dragon with your bare hands. Incredible isn't it? + + return 0; +} + +auto call_fight(Character& character, Creature& creature, Device& device) { + return fight(character, creature, device); +} diff --git a/examples/ast.cpp b/examples/ast.cpp new file mode 100644 index 0000000..e84b033 --- /dev/null +++ b/examples/ast.cpp @@ -0,0 +1,82 @@ +// Copyright (c) 2018-2025 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) + +// clang-format off + +// tag::ast[] + +#include + +#include +#include + +struct Node { + virtual ~Node() {} +}; + +struct Literal : Node { + explicit Literal(int value) : value(value) {} + + int value; +}; + +struct Plus : Node { + Plus(virtual_ptr left, virtual_ptr right) + : left(left), right(right) {} + + virtual_ptr left, right; +}; + +struct Negate : Node { + explicit Negate(virtual_ptr node) : child(node) {} + + virtual_ptr child; +}; + +BOOST_OPENMETHOD(value, (virtual_ptr), int); + +BOOST_OPENMETHOD_OVERRIDE(value, (virtual_ptr node), int) { + return node->value; +} + +BOOST_OPENMETHOD_OVERRIDE(value, (virtual_ptr node), int) { + return value(node->left) + value(node->right); +} + +BOOST_OPENMETHOD_OVERRIDE(value, (virtual_ptr node), int) { + return -value(node->child); +} + +BOOST_OPENMETHOD_CLASSES(Node, Literal, Plus, Negate); + +int main() { + boost::openmethod::initialize(); + + Literal one(1), two(2); + Plus sum(one, two); + Negate neg(sum); + + std::cout << value(neg) << "\n"; // -3 + + return 0; +} +// end::ast[] + +int negate(virtual_ptr node) { + return -value(node); +} + +#define main alt_main + +int main() { +// tag::final[] + Literal one(1); + Negate neg(boost::openmethod::final_virtual_ptr(one)); +// end::final[] + + std::cout << value(boost::openmethod::final_virtual_ptr(neg)) << "\n"; // -3 + + return 0; +} diff --git a/examples/ast_unique_ptr.cpp b/examples/ast_unique_ptr.cpp new file mode 100644 index 0000000..a44d976 --- /dev/null +++ b/examples/ast_unique_ptr.cpp @@ -0,0 +1,71 @@ +// Copyright (c) 2018-2025 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) + +// clang-format off + +// tag::ast[] + +#include +#include + +#include +#include +#include + +using boost::openmethod::unique_virtual_ptr; +using boost::openmethod::make_unique_virtual; + +struct Node { + virtual ~Node() {} +}; + +struct Literal : Node { + Literal(int value) : value(value) {} + + int value; +}; + +struct Plus : Node { + Plus(unique_virtual_ptr left, unique_virtual_ptr right) + : left(std::move(left)), right(std::move(right)) {} + + unique_virtual_ptr left, right; +}; + +struct Negate : Node { + Negate(unique_virtual_ptr node) : child(std::move(node)) {} + + unique_virtual_ptr child; +}; + +BOOST_OPENMETHOD(value, (virtual_ptr), int); + +BOOST_OPENMETHOD_OVERRIDE(value, (virtual_ptr node), int) { + return node->value; +} + +BOOST_OPENMETHOD_OVERRIDE(value, (virtual_ptr node), int) { + return value(node->left) + value(node->right); +} + +BOOST_OPENMETHOD_OVERRIDE(value, (virtual_ptr node), int) { + return -value(node->child); +} + +BOOST_OPENMETHOD_CLASSES(Node, Literal, Plus, Negate); + +int main() { + boost::openmethod::initialize(); + + auto expr = make_unique_virtual( + make_unique_virtual( + make_unique_virtual(1), + make_unique_virtual(2))); + + std::cout << value(expr) << "\n"; // -3 + + return 0; +} +// end::ast[] diff --git a/examples/asteroids.cpp b/examples/asteroids.cpp new file mode 100644 index 0000000..888ff88 --- /dev/null +++ b/examples/asteroids.cpp @@ -0,0 +1,68 @@ +// asteroids.cpp +// Copyright (c) 2018-2025 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) + +// Example for Wikipedia + +#include +#include + +class Thing { + public: + virtual ~Thing() { + } +}; + +class Asteroid : public Thing {}; + +class Spaceship : public Thing {}; + +BOOST_OPENMETHOD_CLASSES(Thing, Spaceship, Asteroid); + +BOOST_OPENMETHOD(collideWith, (virtual_ptr, virtual_ptr), void); + +BOOST_OPENMETHOD_OVERRIDE( + collideWith, (virtual_ptr left, virtual_ptr right), void) { + // default collision handling +} + +BOOST_OPENMETHOD_OVERRIDE( + collideWith, (virtual_ptr left, virtual_ptr right), + void) { + // handle Asteroid-Asteroid collision +} + +BOOST_OPENMETHOD_OVERRIDE( + collideWith, (virtual_ptr left, virtual_ptr right), + void) { + // handle Asteroid-Spaceship collision +} + +BOOST_OPENMETHOD_OVERRIDE( + collideWith, (virtual_ptr left, virtual_ptr right), + void) { + // handle Spaceship-Asteroid collision +} + +BOOST_OPENMETHOD_OVERRIDE( + collideWith, (virtual_ptr left, virtual_ptr right), + void) { + // handle Spaceship-Spaceship collision +} + +int main() { + boost::openmethod::initialize(); + + Asteroid a1, a2; + Spaceship s1, s2; + + collideWith(a1, a2); + collideWith(a1, s1); + + collideWith(s1, s2); + collideWith(s1, a1); + + return 0; +} diff --git a/examples/core_api.cpp b/examples/core_api.cpp new file mode 100644 index 0000000..4f2fac3 --- /dev/null +++ b/examples/core_api.cpp @@ -0,0 +1,103 @@ +// Copyright (c) 2018-2025 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) + +#include + +#include +#include + +struct Animal { + virtual ~Animal() = default; +}; + +struct Cat : Animal {}; + +struct Dog : Animal {}; + +struct Bulldog : Dog {}; + +using namespace boost::openmethod; + +// tag::method[] + +#include + +class BOOST_OPENMETHOD_NAME(poke); + +using poke = method< + BOOST_OPENMETHOD_NAME(poke)(std::ostream&, virtual_ptr), void>; +// end::method[] + +// tag::poke_cat[] +auto poke_cat(std::ostream& os, virtual_ptr cat) { + os << "hiss"; +} + +static poke::override override_poke_cat; +// end::poke_cat[] + +// tag::poke_dog[] +#include + +auto poke_dog(std::ostream& os, virtual_ptr dog) { + os << "bark"; +} + +BOOST_OPENMETHOD_REGISTER(poke::override); +// end::poke_dog[] + +// tag::poke_bulldog[] +auto poke_bulldog(std::ostream& os, virtual_ptr dog) -> void { + poke::next(os, dog); + os << " and bite"; +} + +BOOST_OPENMETHOD_REGISTER(poke::override); +// end::poke_bulldog[] + +class BOOST_OPENMETHOD_NAME(pet); + +auto pet_cat(std::ostream& os, virtual_ptr cat) { + os << "purr"; +} + +auto pet_dog(std::ostream& os, virtual_ptr dog) { + os << "wag tail"; +} + +using pet = method< + BOOST_OPENMETHOD_NAME(pet)(std::ostream&, virtual_ptr), void>; + +BOOST_OPENMETHOD_REGISTER(pet::override); + +// tag::use_classes[] +BOOST_OPENMETHOD_REGISTER(use_classes); +// end::use_classes[] + +// tag::main[] +int main() { + boost::openmethod::initialize(); + + std::unique_ptr a(new Cat); + std::unique_ptr b(new Dog); + std::unique_ptr c(new Bulldog); + + poke::fn(std::cout, *a); // prints "hiss" + std::cout << "\n"; + + poke::fn(std::cout, *b); // prints "bark" + std::cout << "\n"; + + poke::fn(std::cout, *c); // prints "bark and bite" + std::cout << "\n"; + // end::main[] + + pet::fn(std::cout, *a); // prints "purr" + std::cout << "\n"; + // tag::main[] + + return 0; + // end::main[] +} diff --git a/examples/custom_rtti.cpp b/examples/custom_rtti.cpp new file mode 100644 index 0000000..6ae01b2 --- /dev/null +++ b/examples/custom_rtti.cpp @@ -0,0 +1,99 @@ +// Copyright (c) 2018-2025 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) + +struct Animal { + Animal(unsigned type) : type(type) { + } + + virtual ~Animal() = default; + + unsigned type; + static constexpr unsigned static_type = 1; +}; + +struct Cat : Animal { + Cat() : Animal(static_type) { + } + + static constexpr unsigned static_type = 2; +}; + +struct Dog : Animal { + Dog() : Animal(static_type) { + } + + static constexpr unsigned static_type = 3; +}; + +#include +#include + +// tag::facet[] +namespace bom = boost::openmethod; + +struct custom_rtti : bom::policies::rtti { + template + static bom::type_id static_type() { + if constexpr (std::is_base_of_v) { + return T::static_type; + } else { + return 0; + } + } + + template + static bom::type_id dynamic_type(const T& obj) { + if constexpr (std::is_base_of_v) { + return obj.type; + } else { + return 0; + } + } +}; +// end::facet[] + +// tag::policy[] +struct custom_policy : bom::policies::basic_policy< + custom_policy, custom_rtti, + bom::policies::vptr_vector> {}; + +#define BOOST_OPENMETHOD_DEFAULT_POLICY custom_policy +// end::policy[] + +// tag::example[] +#include + +#include +#include + +BOOST_OPENMETHOD(poke, (std::ostream&, virtual_ptr), void); + +BOOST_OPENMETHOD_OVERRIDE( + poke, (std::ostream & os, virtual_ptr cat), void) { + os << "hiss"; +} + +BOOST_OPENMETHOD_OVERRIDE( + poke, (std::ostream & os, virtual_ptr dog), void) { + os << "bark"; +} + +BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog); + +int main() { + boost::openmethod::initialize(); + + std::unique_ptr a(new Cat); + std::unique_ptr b(new Dog); + + poke(std::cout, *a); // prints "hiss" + std::cout << "\n"; + + poke(std::cout, *b); // prints "bark" + std::cout << "\n"; + + return 0; +} +// end::example[] diff --git a/examples/deferred_custom_rtti.cpp b/examples/deferred_custom_rtti.cpp new file mode 100644 index 0000000..54bdda4 --- /dev/null +++ b/examples/deferred_custom_rtti.cpp @@ -0,0 +1,155 @@ +// Copyright (c) 2018-2025 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) + +// tag::classes[] +struct custom_type_info { + static unsigned last; + unsigned id = ++last; +}; + +unsigned custom_type_info::last; + +struct Animal { + Animal() { + type = type_info.id; + } + + virtual ~Animal() = default; + + virtual void* cast_impl(unsigned target) { + if (type_info.id == target) { + return this; + } else { + return nullptr; + } + } + + template + Class* cast() { + return reinterpret_cast(cast_impl(Class::type_info.id)); + } + + static custom_type_info type_info; + unsigned type; +}; + +custom_type_info Animal::type_info; + +struct Cat : virtual Animal { + Cat() { + type = type_info.id; + } + + virtual void* cast_impl(unsigned target) { + if (type_info.id == target) { + return this; + } else { + return Animal::cast_impl(target); + } + } + + static custom_type_info type_info; +}; + +custom_type_info Cat::type_info; +// end::classes[] + +struct Dog : virtual Animal { + Dog() { + type = type_info.id; + } + + virtual void* cast_impl(unsigned target) { + if (type_info.id == target) { + return this; + } else { + return Animal::cast_impl(target); + } + } + + static custom_type_info type_info; +}; + +#include +#include + +namespace bom = boost::openmethod; + +struct custom_rtti : bom::policies::rtti { + template + static bom::type_id static_type() { + if constexpr (std::is_base_of_v) { + return T::type_info.id; + } else { + return 0; + } + } + + template + static bom::type_id dynamic_type(const T& obj) { + if constexpr (std::is_base_of_v) { + return obj.type; + } else { + return 0; + } + } + + // tag::dynamic_cast_ref[] + // to support virtual inheritance: + template + static Derived dynamic_cast_ref(Base&& obj) { + using base_type = std::remove_reference_t; + if constexpr (std::is_base_of_v) { + return *obj.template cast>(); + } else { + abort(); // not supported + } + } + // end::dynamic_cast_ref[] +}; + +struct custom_policy + : bom::policies::basic_policy< + custom_policy, custom_rtti, bom::policies::deferred_static_rtti, + bom::policies::vptr_vector> {}; + +#define BOOST_OPENMETHOD_DEFAULT_POLICY custom_policy + +#include + +#include +#include + +BOOST_OPENMETHOD(poke, (std::ostream&, virtual_ptr), void); + +BOOST_OPENMETHOD_OVERRIDE( + poke, (std::ostream & os, virtual_ptr cat), void) { + os << "hiss"; +} + +BOOST_OPENMETHOD_OVERRIDE( + poke, (std::ostream & os, virtual_ptr dog), void) { + os << "bark"; +} + +BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog); + +custom_type_info Dog::type_info; + +int main() { + boost::openmethod::initialize(); + + std::unique_ptr a(new Cat); + std::unique_ptr b(new Dog); + + poke(std::cout, *a); // prints "hiss" + std::cout << "\n"; + + poke(std::cout, *b); // prints "bark" + std::cout << "\n"; + + return 0; +} +// end::example[] diff --git a/examples/friendship.cpp b/examples/friendship.cpp new file mode 100644 index 0000000..9084caa --- /dev/null +++ b/examples/friendship.cpp @@ -0,0 +1,100 @@ +// Copyright (c) 2018-2025 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) + +// clang-format off + +#include +#include +#include + +#ifdef FRIEND_ALL + +// tag::friend_all[] +class Cat; +class Dog; + +class Animal { +// end::friend_all[] + + public: + Animal(std::string name) : name(name) { + } + virtual ~Animal() = default; +// tag::friend_all[] + // ... + private: + std::string name; + + template friend struct BOOST_OPENMETHOD_OVERRIDERS(poke); +}; +// end::friend_all[] + +#else + +// tag::friend[] +class Cat; +class Dog; + +template struct BOOST_OPENMETHOD_OVERRIDERS(poke); + +class Animal { + // ... +// end::friend[] + public: + Animal(std::string name) : name(name) { + } + virtual ~Animal() = default; + +// tag::friend[] + private: + std::string name; + + friend struct BOOST_OPENMETHOD_OVERRIDERS(poke))>; + friend struct BOOST_OPENMETHOD_OVERRIDERS(poke))>; +}; +// end::friend[] + +#endif + +class Cat : public Animal { + using Animal::Animal; +}; + +class Dog : public Animal { + using Animal::Animal; +}; + +class Animal; + +BOOST_OPENMETHOD(poke, (std::ostream&, virtual_ptr), void); + +BOOST_OPENMETHOD_OVERRIDE( + poke, (std::ostream& os, virtual_ptr cat), void) { + os << cat->name << " hisses"; +} + +BOOST_OPENMETHOD_OVERRIDE( + poke, (std::ostream& os, virtual_ptr dog), void) { + os << dog->name << " barks"; +} + +BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog); + +#include + +int main() { + boost::openmethod::initialize(); + + std::unique_ptr a(new Cat("Felix")); + std::unique_ptr b(new Dog("Snoopy")); + + poke(std::cout, *a); // Felix hisses + std::cout << ".\n"; + + poke(std::cout, *b); // Snoopy barks + std::cout << ".\n"; + + return 0; +} diff --git a/examples/friendship_across_namespaces.cpp b/examples/friendship_across_namespaces.cpp new file mode 100644 index 0000000..a2865b7 --- /dev/null +++ b/examples/friendship_across_namespaces.cpp @@ -0,0 +1,114 @@ +// Copyright (c) 2018-2025 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) + +// clang-format off + +#include +#include +#include + +#ifdef FRIEND_ALL + +// tag::friend_all[] + +namespace pets { +template struct BOOST_OPENMETHOD_OVERRIDERS(poke); +} + +namespace core { +class Animal { +// end::friend_all[] + +// tag::friend_all[] + public: + Animal(std::string name) : name(name) { + } + virtual ~Animal() = default; +// ... + private: + std::string name; + + template friend struct BOOST_OPENMETHOD_OVERRIDERS(pets::poke); +}; + +BOOST_OPENMETHOD(poke, (std::ostream&, virtual_ptr), void); +} +#else + +// tag::friend[] +namespace pets { +struct Cat; +struct Dog; +template struct BOOST_OPENMETHOD_OVERRIDERS(poke); +} // namespace pets + +namespace core { +class Animal { + public: + Animal(std::string name) : name(name) { + } + virtual ~Animal() = default; +// end::friend[] + +// tag::friend[] +// ... + private: + std::string name; + + friend struct BOOST_OPENMETHOD_OVERRIDERS(pets::poke)< + void(std::ostream&, virtual_ptr)>; + friend struct BOOST_OPENMETHOD_OVERRIDERS(pets::poke)< + void(std::ostream&, virtual_ptr)>; +}; + +BOOST_OPENMETHOD(poke, (std::ostream&, virtual_ptr), void); +} + +// end::friend[] +#endif + +// tag::friend[] +namespace pets { + +struct Cat : core::Animal { + using Animal::Animal; +}; + +struct Dog : core::Animal { + using Animal::Animal; +}; + +BOOST_OPENMETHOD_OVERRIDE( + poke, + (std::ostream & os, virtual_ptr cat), + void) { + os << cat->name << " hisses"; +} + +BOOST_OPENMETHOD_OVERRIDE( + poke, (std::ostream & os, virtual_ptr dog), void) { + os << dog->name << " barks"; +} + +BOOST_OPENMETHOD_CLASSES(core::Animal, Cat, Dog); +} // namespace pets +// end::friend[] + +#include + +int main() { + boost::openmethod::initialize(); + + std::unique_ptr a(new pets::Cat("Felix")); + std::unique_ptr b(new pets::Dog("Snoopy")); + + core::poke(std::cout, *a); // Felix hisses + std::cout << ".\n"; + + core::poke(std::cout, *b); // Snoopy barks + std::cout << ".\n"; + + return 0; +} diff --git a/examples/hello_world.cpp b/examples/hello_world.cpp new file mode 100644 index 0000000..c07b927 --- /dev/null +++ b/examples/hello_world.cpp @@ -0,0 +1,167 @@ +// Copyright (c) 2018-2025 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) + +// clang-format off + +// tag::domain_classes[] +#include + +struct Animal { + Animal(std::string name) : name(name) {} + std::string name; + virtual ~Animal() = default; +}; + +struct Cat : Animal { + using Animal::Animal; +}; + +struct Dog : Animal { + using Animal::Animal; +}; + +struct Bulldog : Dog { + using Dog::Dog; +}; +// end::domain_classes[] + +// tag::method[] +#include +#include + +BOOST_OPENMETHOD( + poke, // method name + (std::ostream&, virtual_ptr), // method signature + void); // return type +// end::method[] + +// tag::overriders[] +BOOST_OPENMETHOD_OVERRIDE( + poke, // method name + (std::ostream & os, virtual_ptr cat), // overrider signature + void) { // return type + os << cat->name << " hisses"; // overrider body +} + +BOOST_OPENMETHOD_OVERRIDE(poke, (std::ostream & os, virtual_ptr dog), void) { + os << dog->name << " barks"; +} +// end::overriders[] + +// tag::next[] +BOOST_OPENMETHOD_OVERRIDE( + poke, (std::ostream & os, virtual_ptr dog), void) { + next(os, dog); // call base overrider + os << " and bites back"; +} +// end::next[] + +// tag::classes[] +BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog, Bulldog); +// end::classes[] + +// tag::multi[] +BOOST_OPENMETHOD( + encounter, + (std::ostream&, virtual_ptr, virtual_ptr), void); + +// 'encounter' catch-all implementation. +BOOST_OPENMETHOD_OVERRIDE( + encounter, + (std::ostream & os, virtual_ptr a, virtual_ptr b), void) { + os << a->name << " and " << b->name << " ignore each other"; +} + +// Add definitions for specific pairs of animals. +BOOST_OPENMETHOD_OVERRIDE( + encounter, + (std::ostream & os, virtual_ptr dog1, virtual_ptr dog2), void) { + os << "Both wag tails"; +} + +BOOST_OPENMETHOD_OVERRIDE( + encounter, (std::ostream & os, virtual_ptr dog, virtual_ptr cat), + void) { + os << dog->name << " chases " << cat->name; +} + +BOOST_OPENMETHOD_OVERRIDE( + encounter, (std::ostream & os, virtual_ptr cat, virtual_ptr dog), + void) { + os << cat->name << " runs away from " << dog->name; +} +// end::multi[] + +// tag::main[] +#include + // only needed in the file that calls boost::openmethod::initialize() + +int main() { + boost::openmethod::initialize(); + // ... + // end::main[] + + // tag::call[] + std::unique_ptr felix(new Cat("Felix")); + std::unique_ptr snoopy(new Dog("Snoopy")); + std::unique_ptr hector(new Bulldog("Hector")); + + poke(std::cout, *felix); // Felix hisses + std::cout << ".\n"; + + poke(std::cout, *snoopy); // Snoopy barks + std::cout << ".\n"; + + poke(std::cout, *hector); // Hector barks and bites + std::cout << ".\n"; + // end::call[] + + // tag::multi_call[] + // cat and dog + encounter(std::cout, *felix, *snoopy); // Felix runs away from Snoopy + std::cout << ".\n"; + + // dog and cat + encounter(std::cout, *snoopy, *felix); // Snoopy chases Felix + std::cout << ".\n"; + + // dog and dog + encounter(std::cout, *snoopy, *hector); // Both wag tails + std::cout << ".\n"; + + // cat and cat + std::unique_ptr tom(new Cat("Tom")); + encounter(std::cout, *felix, *tom); // Felix and Tom ignore each other + std::cout << ".\n"; + // end::multi_call[] + + + return 0; + // tag::main[] +} +// end::main[] + +// tag::call_poke_via_virtual_ptr[] +void call_poke_via_virtual_ptr(std::ostream& os, virtual_ptr a) { + poke(os, a); +} +// end::call_poke_via_virtual_ptr[] + +// tag::call_poke_via_final_virtual_ptr[] +void call_poke_via_final_virtual_ptr(std::ostream& os, Cat& cat) { + poke(os, boost::openmethod::final_virtual_ptr(cat)); +} +// end::call_poke_via_final_virtual_ptr[] + +// tag::call_poke_via_ref[] +void call_poke_via_ref(std::ostream& os, Animal& a) { + poke(os, a); +} +// end::call_poke_via_ref[] + +void call_encounter( + std::ostream& os, virtual_ptr a, virtual_ptr b) { + encounter(os, a, b); +} diff --git a/examples/matrix.cpp b/examples/matrix.cpp new file mode 100644 index 0000000..6519795 --- /dev/null +++ b/examples/matrix.cpp @@ -0,0 +1,147 @@ +// Copyright (c) 2018-2025 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) + +#include +#include +#include +#include + +#include +#include +#include + +using std::make_shared; +using std::shared_ptr; +using std::string; + +using boost::openmethod::make_shared_virtual; +using boost::openmethod::shared_virtual_ptr; + +struct matrix { + virtual ~matrix() { + } + virtual double at(int row, int col) const = 0; + // ... +}; + +struct dense_matrix : matrix { + virtual double at(int row, int col) const { + return 0; + } +}; + +struct diagonal_matrix : matrix { + virtual double at(int row, int col) const { + return 0; + } +}; + +BOOST_OPENMETHOD_CLASSES(matrix, dense_matrix, diagonal_matrix); + +BOOST_OPENMETHOD(to_json, (virtual_ptr), string); + +BOOST_OPENMETHOD_OVERRIDE( + to_json, (virtual_ptr m), string) { + return "json for dense matrix..."; +} + +BOOST_OPENMETHOD_OVERRIDE( + to_json, (virtual_ptr m), string) { + return "json for diagonal matrix..."; +} + +// ----------------------------------------------------------------------------- +// matrix * matrix + +BOOST_OPENMETHOD( + times, (shared_virtual_ptr, shared_virtual_ptr), + shared_virtual_ptr); + +// catch-all matrix * matrix -> dense_matrix +BOOST_OPENMETHOD_OVERRIDE( + times, + (shared_virtual_ptr a, shared_virtual_ptr b), + shared_virtual_ptr) { + return make_shared(); +} + +// diagonal_matrix * diagonal_matrix -> diagonal_matrix +BOOST_OPENMETHOD_OVERRIDE( + times, + (shared_virtual_ptr a, + shared_virtual_ptr b), + shared_virtual_ptr) { + return make_shared_virtual(); +} + +inline shared_virtual_ptr +operator*(shared_ptr a, shared_ptr b) { + return times(a, b); +} + +// ----------------------------------------------------------------------------- +// scalar * matrix + +BOOST_OPENMETHOD( + times, (double, shared_virtual_ptr), + shared_virtual_ptr); + +// catch-all matrix * scalar -> dense_matrix +BOOST_OPENMETHOD_OVERRIDE( + times, (double a, shared_virtual_ptr b), + shared_virtual_ptr) { + return make_shared_virtual(); +} + +BOOST_OPENMETHOD_OVERRIDE( + times, (double a, shared_virtual_ptr b), + shared_virtual_ptr) { + return make_shared_virtual(); +} + +// ----------------------------------------------------------------------------- +// matrix * scalar + +// just swap +inline shared_virtual_ptr +times(shared_virtual_ptr a, double b) { + return times(b, a); +} + +// ----------------------------------------------------------------------------- +// main + +#define check(expr) \ + { \ + if (!(expr)) { \ + cerr << #expr << " failed\n"; \ + } \ + } + +int main() { + using std::cerr; + using std::cout; + + boost::openmethod::initialize(); + + shared_ptr a = make_shared(); + shared_ptr b = make_shared(); + double s = 1; + +#ifndef _MSC_VER +#pragma clang diagnostic ignored "-Wpotentially-evaluated-expression" +#endif + + check(typeid(*times(a, a)) == typeid(dense_matrix)); + check(typeid(*times(a, b)) == typeid(dense_matrix)); + check(typeid(*times(b, b)) == typeid(diagonal_matrix)); + check(typeid(*times(s, a)) == typeid(dense_matrix)); + check(typeid(*times(s, b)) == typeid(diagonal_matrix)); + + cout << to_json(*a) << "\n"; // json for dense matrix + cout << to_json(*b) << "\n"; // json for diagonal matrix + + return 0; +} diff --git a/examples/next.cpp b/examples/next.cpp new file mode 100644 index 0000000..235658c --- /dev/null +++ b/examples/next.cpp @@ -0,0 +1,73 @@ +// next.cpp +// Copyright (c) 2018-2025 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) + +// Example taken Dylan's documentation, see +// http://opendylan.org/documentation/intro-dylan/multiple-dispatch.html + +#include + +#include +#include + +using namespace std; + +struct Vehicle { + virtual ~Vehicle() { + } +}; + +//[ car +struct Car : Vehicle {}; + +struct Truck : Vehicle {}; + +struct Inspector { + virtual ~Inspector() { + } +}; + +struct StateInspector : Inspector {}; + +BOOST_OPENMETHOD_CLASSES(Vehicle, Car, Truck, Inspector, StateInspector); + +BOOST_OPENMETHOD( + inspect, (virtual_ptr, virtual_ptr), void); + +BOOST_OPENMETHOD_OVERRIDE( + inspect, (virtual_ptr v, virtual_ptr i), + void) { + cout << "Inspect vehicle.\n"; +} + +BOOST_OPENMETHOD_OVERRIDE( + inspect, (virtual_ptr v, virtual_ptr i), void) { + next(v, i); + cout << "Inspect seat belts.\n"; +} + +BOOST_OPENMETHOD_OVERRIDE( + inspect, (virtual_ptr v, virtual_ptr i), + void) { + next(v, i); + cout << "Check road tax.\n"; +} + +int main() { + boost::openmethod::initialize(); + + const Vehicle& vehicle1 = Car(); + const Inspector& inspector1 = StateInspector(); + const Vehicle& vehicle2 = Truck(); + const Inspector& inspector2 = Inspector(); + + cout << "First inspection:\n"; + inspect(vehicle1, inspector1); + + cout << "\nSecond inspection:\n"; + inspect(vehicle2, inspector2); + + return 0; +} diff --git a/examples/slides.cpp b/examples/slides.cpp new file mode 100644 index 0000000..0eb6fa4 --- /dev/null +++ b/examples/slides.cpp @@ -0,0 +1,317 @@ +// clang-format off + +#include +#include +#include +#include +#include +#include + +using namespace std; + +namespace nodes { + +struct Number; +struct Plus; +struct Times; + +struct Node { + virtual ~Node() {} + virtual int value() const = 0; + virtual string to_rpn() const = 0; + + struct Visitor { + virtual void accept(const Number& expr) = 0; + virtual void accept(const Plus& expr) = 0; + virtual void accept(const Times& expr) = 0; + }; + + virtual void visit(Visitor& viz) const = 0; +}; + +struct Number : Node { + explicit Number(int value) : val(value) { } + int value() const override { return val; } + string to_rpn() const override { return to_string(val); } + void visit(Visitor& viz) const override { viz.accept(*this); } + int val; +}; + +struct Plus : Node { + Plus(const Node& left, const Node& right) : left(left), right(right) { } + int value() const override { return left.value() + right.value(); } + string to_rpn() const override { return left.to_rpn() + " " + right.to_rpn() + " +"; } + void visit(Visitor& viz) const override { viz.accept(*this); } + const Node& left; const Node& right; +}; + +struct Times : Node { + Times(const Node& left, const Node& right) : left(left), right(right) { } + int value() const override { return left.value() * right.value(); } + string to_rpn() const override { return left.to_rpn() + " " + right.to_rpn() + " &"; } + void visit(Visitor& viz) const override { viz.accept(*this); } + const Node& left; const Node& right; +}; + +} + +namespace typeswitch { + +using namespace nodes; + +string to_rpn(const Node& node) { + if (auto expr = dynamic_cast(&node)) { + return to_string(expr->value()); + } else if (auto expr = dynamic_cast(&node)) { + return to_rpn(expr->left) + " " + to_rpn(expr->right) + " +"; + } else if (auto expr = dynamic_cast(&node)) { + return to_rpn(expr->left) + " " + to_rpn(expr->right) + " *"; + } + throw runtime_error("unknown node type"); +} +} + +namespace visitor { + +using namespace nodes; + +struct RPNVisitor : Node::Visitor { + void accept(const Number& expr) { + result = to_string(expr.val); + } + void accept(const Plus& expr) { + expr.left.visit(*this); + string l = result; + expr.right.visit(*this); + result = l + " " + result + " +"; + } + void accept(const Times& expr) { + expr.left.visit(*this); + string l = result; + expr.right.visit(*this); + result = l + " " + result + " *"; + } + string result; +}; + +string to_rpn(const Node& node) { + RPNVisitor viz; + node.visit(viz); + return viz.result; +} + +} + +namespace funtable { + +using namespace nodes; + +unordered_map RPNformatters; + +string to_rpn(const Node& node) { + return RPNformatters[typeid(node)](node); +} + +struct Init { + Init() { + RPNformatters[typeid(Number)] = [](const Node& node) { + return to_string(static_cast(node).val); }; + RPNformatters[typeid(Plus)] = [](const Node& node) { + auto expr = static_cast(node); + return to_rpn(expr.left) + " " + to_rpn(expr.right) + " +"; }; + RPNformatters[typeid(Times)] = [](const Node& node) { + auto expr = static_cast(node); + return to_rpn(expr.left) + " " + to_rpn(expr.right) + " *"; }; + } +} init; +} + +#include +#include +#include + +namespace openmethods { + +using namespace nodes; + +BOOST_OPENMETHOD_CLASSES(Node, Number, Plus, Times); + +BOOST_OPENMETHOD(to_rpn, (virtual_ptr), string); + +BOOST_OPENMETHOD_OVERRIDE(to_rpn, (virtual_ptr expr), string) { + return std::to_string(expr->val); +} + +BOOST_OPENMETHOD_OVERRIDE(to_rpn, (virtual_ptr expr), string) { + return to_rpn(expr->left) + " " + to_rpn(expr->right) + " +"; +} + +BOOST_OPENMETHOD_OVERRIDE(to_rpn, (virtual_ptr expr), string) { + return to_rpn(expr->left) + " " + to_rpn(expr->right) + " *"; +} + +BOOST_OPENMETHOD(value, (virtual_ptr), int); + +BOOST_OPENMETHOD_OVERRIDE(value, (virtual_ptr expr), int) { + return expr->val; +} + +BOOST_OPENMETHOD_OVERRIDE(value, (virtual_ptr expr), int) { + return value(expr->left) + value(expr->right); +} + +BOOST_OPENMETHOD_OVERRIDE(value, (virtual_ptr expr), int) { + return value(expr->left) * value(expr->right); +} + +} + +namespace virtual_ptr_demo { + +using namespace boost::openmethod; +using namespace nodes; + +BOOST_OPENMETHOD(value, (virtual_ptr), int); + +int call_via_vptr(virtual_ptr node) { + return value(node); +} + +BOOST_OPENMETHOD_OVERRIDE(value, (virtual_ptr expr), int) { + return value(expr->left) + value(expr->right); +} + +auto make_node_ptr(const Node& node) { + return virtual_ptr(node); +} + +auto make_final_node_ptr(const Plus& node) { + return final_virtual_ptr(node); +} + +} + +namespace unique_virtual_ptr_demo { + +using namespace boost::openmethod; + +struct Number; +struct Plus; +struct Times; + +struct Node { + virtual ~Node() {} +}; + +struct Number : Node { + explicit Number(int value) : val(value) { } + int val; +}; + +struct Plus : Node { + Plus(unique_virtual_ptr&& left, unique_virtual_ptr&& right) + : left(std::move(left)), right(std::move(right)) { } + unique_virtual_ptr left, right; +}; + +struct Times : Node { + Times(unique_virtual_ptr&& left, unique_virtual_ptr&& right) + : left(std::move(left)), right(std::move(right)) { } + unique_virtual_ptr left, right; +}; + +BOOST_OPENMETHOD_CLASSES(Node, Number, Plus, Times); + +BOOST_OPENMETHOD(value, (virtual_ptr), int); + +BOOST_OPENMETHOD_OVERRIDE(value, (virtual_ptr expr), int) { + return value(expr->left) + value(expr->right); +} + +BOOST_OPENMETHOD_OVERRIDE(value, (virtual_ptr expr), int) { + return expr->val; +} + +BOOST_OPENMETHOD_OVERRIDE(value, (virtual_ptr expr), int) { + return value(expr->left) * value(expr->right); +} + +BOOST_OPENMETHOD(to_rpn, (virtual_ptr), string); + +BOOST_OPENMETHOD_OVERRIDE(to_rpn, (virtual_ptr expr), string) { + return std::to_string(expr->val); +} + +BOOST_OPENMETHOD_OVERRIDE(to_rpn, (virtual_ptr expr), string) { + return to_rpn(expr->left) + " " + to_rpn(expr->right) + " +"; +} + +BOOST_OPENMETHOD_OVERRIDE(to_rpn, (virtual_ptr expr), string) { + return to_rpn(expr->left) + " " + to_rpn(expr->right) + " *"; +} + +} + +namespace core_api { + +using namespace boost::openmethod; +using namespace nodes; + +use_classes use_node_classes; + +struct value_id; +using value = method), int>; + +int number_value(virtual_ptr node) { + return node->val; +} +value::override add_number_value; + +template +int binary_op(virtual_ptr expr) { + return Op()(value::fn(expr->left), value::fn(expr->right)); +} + +BOOST_OPENMETHOD_REGISTER(value::override>>); +BOOST_OPENMETHOD_REGISTER(value::override>>); + +} + +int main() { + boost::openmethod::initialize(); + + { + using namespace nodes; + + Number n2(2), n3(3), n4(4); + Plus sum(n3, n4); + Times product(n2, sum); + + const Node& expr = product; + cout << expr.value() << "\n"; + + cout << expr.value() << "\n"; + cout << typeswitch::to_rpn(expr) << " = " << expr.value() << "\n"; + cout << visitor::to_rpn(expr) << " = " << expr.value() << "\n"; + cout << funtable::to_rpn(expr) << " = " << expr.value() << "\n"; + + cout << openmethods::to_rpn(expr) << " = " << expr.value() << "\n"; + + cout << core_api::value::fn(expr) << "\n"; + } + + { + using boost::openmethod::make_unique_virtual; + using namespace unique_virtual_ptr_demo; + + auto expr = make_unique_virtual( + make_unique_virtual(2), + make_unique_virtual( + make_unique_virtual(3), make_unique_virtual(4) + )); + + cout << to_rpn(expr) << " = " << value(expr) << "\n"; + } + + return 0; +} diff --git a/examples/synopsis.cpp b/examples/synopsis.cpp new file mode 100644 index 0000000..da552cc --- /dev/null +++ b/examples/synopsis.cpp @@ -0,0 +1,184 @@ +// Copyright (c) 2018-2025 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) + +// clang-format off + +// NOTE: No actual animals were hurt while designing, coding, compiling and +// running this example. + +// ============================================================================= +// Define a few polymorphic classes... + +class Animal { + public: + virtual ~Animal() { + } +}; + +class Dog : public Animal {}; +class Bulldog : public Dog {}; +class Cat : public Animal {}; +class Dolphin : public Animal {}; + +// ============================================================================= +// Add behavior to existing classes, without modifying them. + +#include +#include + +// Classes must be registered: +BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat, Dolphin); + +// ...but it does not have to be in one call to 'BOOST_OPENMETHOD_CLASSES', as long as +// inheritance relationships can be deduced. This allows *adding* classes to an +// existing collection of classes. +BOOST_OPENMETHOD_CLASSES(Dog, Bulldog); + +// Define a uni-method, i.e. a method with a single virtual argument. This is in +// essence a virtual function implemented as a free function. +BOOST_OPENMETHOD(poke, (virtual_ptr, std::ostream&), void); + +// Implement 'poke' for dogs. +BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr dog, std::ostream& os), void) { + os << "bark"; +} + +// Implement 'poke' for bulldogs. They behave like Dogs, but, in addition, they +// fight back. +BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr dog, std::ostream& os), void) { + next(dog, os); // calls "base" method, i.e. definition for Dog + os << " and bite"; +} + +// A multi-method with two virtual arguments... +BOOST_OPENMETHOD(meet, (virtual_ptr, virtual_ptr, std::ostream&), void); + +// 'meet' catch-all implementation. +BOOST_OPENMETHOD_OVERRIDE(meet, (virtual_ptr, virtual_ptr, std::ostream& os), void) { + os << "ignore"; +} + +// Add definitions for specific pairs of animals. +BOOST_OPENMETHOD_OVERRIDE(meet, (virtual_ptr dog1, virtual_ptr dog2, std::ostream& os), void) { + os << "wag tail"; +} + +BOOST_OPENMETHOD_OVERRIDE(meet, (virtual_ptr dog, virtual_ptr cat, std::ostream& os), void) { + os << "chase"; +} + +BOOST_OPENMETHOD_OVERRIDE(meet, (virtual_ptr cat, virtual_ptr dog, std::ostream& os), void) { + os << "run"; +} + +// ============================================================================= +// main + +#include +#include +#include + +int main() { + // Initialise method dispatch tables. + boost::openmethod::initialize(); + + // Create a few objects. + // Note that the actual classes are type-erased to base class Animal! + std::unique_ptr + hector = std::make_unique(), + snoopy = std::make_unique(), + sylvester = std::make_unique(), + flipper = std::make_unique(); + + // Call 'poke'. + std::cout << "poke snoopy: "; + poke(*snoopy, std::cout); // bark + std::cout << "\n"; + + std::cout << "poke hector: "; + poke(*hector, std::cout); // bark and bite + std::cout << "\n"; + + // Call 'meet'. + std::cout << "hector meets sylvester: "; + meet(*hector, *sylvester, std::cout); // chase + std::cout << "\n"; + + std::cout << "sylvester meets hector: "; + meet(*sylvester, *hector, std::cout); // run + std::cout << "\n"; + + std::cout << "hector meets snoopy: "; + meet(*hector, *snoopy, std::cout); // wag tail + std::cout << "\n"; + + std::cout << "hector meets flipper: "; + meet(*hector, *flipper, std::cout); // ignore + std::cout << "\n"; +} + +// Let's look at the code generated by clang++-14 for method call. + +void call_poke(Animal& a, std::ostream& os) { + poke(a, os); + + // Instructions in the same paragraph are independent, thus they can be + // executed in parallel. + + // mov rax, qword ptr [rdi] ; read vptr + // mov rdx, qword ptr [rip + global+24] ; M hash factor (multiply) + + // imul rdx, qword ptr [rax - 8] ; multiply vptr[-1] (&typeid(a)) by M + // mov cl, byte ptr [rip + global+32] ; S hash factor (shift) + + // shr rdx, cl ; shift by S: this is the position of + // ; the method table for the dynamic class of 'a' + // ; in the global hash table + // mov rax, qword ptr [rip + global+40] ; address of global hash table + + // mov rax, qword ptr [rax + 8*rdx] ; method table for the class + // mov rcx, qword ptr [rip + method+96] ; offset of the 'poke' in method table + + // mov rax, qword ptr [rax + 8*rcx] ; read function pointer at offset + + // jmp rax ; tail call +} + +void call_meet(Animal& a, Animal& b, std::ostream& os) { + meet(a, b, os); + + // Instructions in the same paragraph are independent, thus they can be + // executed in parallel. + + // mov r8, qword ptr [rdi] ; vptr of 'a' + // mov r9, qword ptr [rip + global+24] ; M hash factor (multiply) + // mov cl, byte ptr [rip + global+32] ; S hash factor (shift) + + // mov r10, qword ptr [r8 - 8] ; read a.vptr[-1] (&a) + + // imul r10, r9 ; multiply by M + + // shr r10, cl ; index of method table for 'a' + // mov r8, qword ptr [rip + global+40] ; address of global hash table + // mov rax, qword ptr [rsi] ; read vptr of 'b' + // imul r9, qword ptr [rax - 8] ; multiply b.vptr[-1] (&typeid(b)) by M + + // mov rax, qword ptr [r8 + 8*r10] ; method table for 'a' + // shr r9, cl ; index of method table for 'b' + + // mov rcx, qword ptr [rip + method+96] ; offset of 'meet' in method table for 'a' + + // mov r10, qword ptr [rax + 8*rcx] ; pointer to row for 'a' in dispatch table + // mov rcx, qword ptr [r8 + 8*r9] ; method table for 'b' + // mov rax, qword ptr [rip + method+104] ; offset of 'meet' in method table for 'b' + + // mov rax, qword ptr [rcx + 8*rax] ; column of 'b' in dispatch table + + // imul rax, qword ptr [rip + method+112] ; multiply by # of lines in dispatch table + + // mov rax, qword ptr [r10 + 8*rax] ; pointer to function, at dispatch[a][b] + + // jmp rax ; tail call +} diff --git a/examples/throw_error_handler.cpp b/examples/throw_error_handler.cpp new file mode 100644 index 0000000..2942ee0 --- /dev/null +++ b/examples/throw_error_handler.cpp @@ -0,0 +1,64 @@ +// Copyright (c) 2018-2025 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) + +// tag::example[] +#include + +#include + +struct Animal { + virtual ~Animal() = default; +}; + +struct Cat : Animal {}; +struct Dog : Animal {}; + +namespace bom = boost::openmethod; + +struct throw_if_not_implemented : bom::policies::error_handler { + static auto error(const bom::openmethod_error&) -> void { + } + + static auto error(const bom::not_implemented_error& err) -> void { + throw err; + } +}; + +struct throwing_policy + : bom::default_policy::fork::replace< + bom::policies::error_handler, throw_if_not_implemented> {}; + +#define BOOST_OPENMETHOD_DEFAULT_POLICY throwing_policy + +#include +#include + +BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog); + +BOOST_OPENMETHOD(trick, (std::ostream&, virtual_ptr), void); + +BOOST_OPENMETHOD_OVERRIDE( + trick, (std::ostream & os, virtual_ptr dog), void) { + os << "spin\n"; +} + +int main() { + bom::initialize(); + + Cat felix; + Dog hector, snoopy; + std::vector animals = {&hector, &felix, &snoopy}; + + for (auto animal : animals) { + try { + trick(std::cout, *animal); + } catch (bom::not_implemented_error) { + std::cout << "not implemented\n"; + } + } + + return 0; +} +// end::example[] diff --git a/examples/vectored_error_handler.cpp b/examples/vectored_error_handler.cpp new file mode 100644 index 0000000..9e66856 --- /dev/null +++ b/examples/vectored_error_handler.cpp @@ -0,0 +1,54 @@ +// Copyright (c) 2018-2025 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) + +// tag::example[] +#include +#include + +#include +#include + +struct Animal { + virtual ~Animal() = default; +}; + +struct Cat : Animal {}; +struct Dog : Animal {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog); + +BOOST_OPENMETHOD(trick, (std::ostream&, virtual_ptr), void); + +BOOST_OPENMETHOD_OVERRIDE( + trick, (std::ostream & os, virtual_ptr dog), void) { + os << "spin\n"; +} + +int main() { + namespace bom = boost::openmethod; + bom::initialize(); + + bom::default_policy::set_error_handler( + [](const bom::default_policy::error_variant& error) { + if (std::holds_alternative(error)) { + throw std::runtime_error("not implemented"); + } + }); + + Cat felix; + Dog hector, snoopy; + std::vector animals = {&hector, &felix, &snoopy}; + + for (auto animal : animals) { + try { + trick(std::cout, *animal); + } catch (std::runtime_error& error) { + std::cerr << error.what() << "\n"; + } + } + + return 0; +} +// end::example[] diff --git a/examples/virtual_.cpp b/examples/virtual_.cpp new file mode 100644 index 0000000..c8e29e7 --- /dev/null +++ b/examples/virtual_.cpp @@ -0,0 +1,109 @@ +// Copyright (c) 2018-2025 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) + +#include + +#include +#include + +using boost::openmethod::virtual_; + +// clang-format off + +namespace just_virtual { + +// tag::virtual_parameter[] +struct Animal { + virtual ~Animal() = default; +}; + +struct Cat : Animal {}; + +using boost::openmethod::virtual_; + +BOOST_OPENMETHOD(poke, (std::ostream&, virtual_), void); + +BOOST_OPENMETHOD_OVERRIDE(poke, (std::ostream & os, Cat& cat), void) { + os << "hiss"; +} + +BOOST_OPENMETHOD_CLASSES(Animal, Cat); +// end::virtual_parameter[] +} // namespace just_virtual + +namespace virtual_intrusive { + +// tag::virtual_intrusive[] + +class Animal { + protected: + boost::openmethod::vptr_type vptr; + friend auto boost_openmethod_vptr(const Animal& a) { + return a.vptr; + } + + public: + Animal() { + vptr = boost::openmethod::default_policy::static_vptr; + } +}; + +class Cat : public Animal { + public: + Cat() { + vptr = boost::openmethod::default_policy::static_vptr; + } +}; + +BOOST_OPENMETHOD(poke, (std::ostream&, virtual_), void); + +BOOST_OPENMETHOD_OVERRIDE(poke, (std::ostream & os, Cat& cat), void) { + os << "hiss\n"; +} + +BOOST_OPENMETHOD_CLASSES(Animal, Cat); +// end::virtual_intrusive[] +} // namespace virtual_intrusive + +namespace with_vptr { + +// tag::with_vptr[] + +class Animal : public boost::openmethod::with_vptr { +}; + +class Cat : public Animal, public boost::openmethod::with_vptr { +}; + +BOOST_OPENMETHOD(poke, (std::ostream&, virtual_), void); + +BOOST_OPENMETHOD_OVERRIDE(poke, (std::ostream & os, Cat& cat), void) { + os << "hiss\n"; +} +// end::with_vptr[] + +} // namespace with_vptr + +int main() { + boost::openmethod::initialize(); + + { + using namespace just_virtual; + Cat cat; + poke(std::cout, cat); // hiss + } + + { + using namespace virtual_intrusive; + Cat cat; + poke(std::cout, cat); // hiss + } + + { + using namespace with_vptr; + Cat cat; + poke(std::cout, cat); // hiss + } +} diff --git a/examples/virtual_func.cpp b/examples/virtual_func.cpp new file mode 100644 index 0000000..cdfb735 --- /dev/null +++ b/examples/virtual_func.cpp @@ -0,0 +1,62 @@ +// Copyright (c) 2018-2025 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) + +// clang-format off + +// tag::code[] + +#include +#include + +struct Animal { + Animal(std::string name) : name(name) {} + virtual ~Animal() = default; + virtual void poke(std::ostream&) = 0; + std::string name; +}; + +struct Cat : Animal { + using Animal::Animal; + + void poke(std::ostream& os) override { + os << name << " hisses"; + } +}; + +struct Dog : Animal { + using Animal::Animal; + + void poke(std::ostream& os) override { + os << name << " barks"; + } +}; + +struct Bulldog : Dog { + using Dog::Dog; + + void poke(std::ostream& os) override { + Dog::poke(os); + os << " and bites back"; + } +}; + +int main() { + std::unique_ptr a(new Cat("Felix")); + std::unique_ptr b(new Dog("Snoopy")); + std::unique_ptr c(new Bulldog("Hector")); + + a->poke(std::cout); // prints "Felix hisses" + std::cout << ".\n"; + + b->poke(std::cout); // prints "Snoopy barks" + std::cout << ".\n"; + + c->poke(std::cout); // prints "Hector barks and bites back" + std::cout << ".\n"; + + return 0; +} + +// end::code[] diff --git a/examples/virtual_ptr.cpp b/examples/virtual_ptr.cpp new file mode 100644 index 0000000..2566ad2 --- /dev/null +++ b/examples/virtual_ptr.cpp @@ -0,0 +1,71 @@ +// Copyright (c) 2018-2025 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) + +#include +#include +#include + +struct Animal { + virtual ~Animal() = default; +}; + +struct Cat : Animal {}; + +struct Dog : Animal {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Cat, Dog); + +BOOST_OPENMETHOD(yell, (std::ostream&, virtual_ptr), void); + +BOOST_OPENMETHOD_OVERRIDE( + yell, (std::ostream & os, virtual_ptr cat), void) { + os << "hiss"; +} + +BOOST_OPENMETHOD_OVERRIDE( + yell, (std::ostream & os, virtual_ptr dog), void) { + os << "bark"; +} + +BOOST_OPENMETHOD( + encounter, (std::ostream&, virtual_ptr, virtual_ptr), void); + +BOOST_OPENMETHOD_OVERRIDE( + encounter, (std::ostream & os, virtual_ptr dog, virtual_ptr cat), + void) { + yell(os, dog); + os << " and chase"; +} + +BOOST_OPENMETHOD_OVERRIDE( + encounter, (std::ostream & os, virtual_ptr cat, virtual_ptr dog), + void) { + yell(os, cat); + os << " and run"; +} + +int main() { + boost::openmethod::initialize(); + + std::unique_ptr a(new Cat); + std::unique_ptr b(new Dog); + + encounter(std::cout, *a, *b); // prints "hiss and run" + std::cout << "\n"; + + encounter(std::cout, *b, *a); // prints "bark and chase" + std::cout << "\n"; + + return 0; +} + +void call_yell(std::ostream& os, virtual_ptr a) { + yell(os, a); +} + +void call_encounter( + std::ostream& os, virtual_ptr a, virtual_ptr b) { + encounter(os, a, b); +} diff --git a/include/boost/openmethod.hpp b/include/boost/openmethod.hpp new file mode 100644 index 0000000..5e549b4 --- /dev/null +++ b/include/boost/openmethod.hpp @@ -0,0 +1,14 @@ +// Copyright (c) 2018-2025 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) + +#ifndef BOOST_OPENMETHOD_HPP +#define BOOST_OPENMETHOD_HPP + +#include +#include + +using boost::openmethod::virtual_ptr; + +#endif // BOOST_OPENMETHOD_HPP diff --git a/include/boost/openmethod/compiler.hpp b/include/boost/openmethod/compiler.hpp new file mode 100644 index 0000000..e6ac080 --- /dev/null +++ b/include/boost/openmethod/compiler.hpp @@ -0,0 +1,1316 @@ +// Copyright (c) 2018-2025 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) + +#ifndef BOOST_OPENMETHOD_COMPILER_HPP +#define BOOST_OPENMETHOD_COMPILER_HPP + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace boost { +namespace openmethod { +namespace detail { + +template +struct aggregate_reports; + +template +struct aggregate_reports< + mp11::mp_list, mp11::mp_list, + std::void_t> { + using type = typename aggregate_reports< + mp11::mp_list, + mp11::mp_list>::type; +}; + +template +struct aggregate_reports< + mp11::mp_list, mp11::mp_list, Void> { + using type = typename aggregate_reports< + mp11::mp_list, mp11::mp_list>::type; +}; + +template +struct aggregate_reports, mp11::mp_list<>, Void> { + struct type : Reports... {}; +}; + +inline void merge_into(boost::dynamic_bitset<>& a, boost::dynamic_bitset<>& b) { + if (b.size() < a.size()) { + b.resize(a.size()); + } + + for (std::size_t i = 0; i < a.size(); ++i) { + if (a[i]) { + b[i] = true; + } + } +} + +inline void set_bit(boost::dynamic_bitset<>& mask, std::size_t bit) { + if (bit >= mask.size()) { + mask.resize(bit + 1); + } + + mask[bit] = true; +} + +struct generic_compiler { + + struct method; + + struct parameter { + struct method* method; + std::size_t param; + }; + + struct vtbl_entry { + std::size_t method_index, vp_index, group_index; + }; + + struct class_ { + bool is_abstract = false; + std::vector type_ids; + std::vector transitive_bases; + std::vector direct_bases; + std::vector direct_derived; + std::unordered_set transitive_derived; + std::vector used_by_vp; + boost::dynamic_bitset<> used_slots; + boost::dynamic_bitset<> reserved_slots; + std::size_t first_slot = 0; + std::size_t mark = 0; // temporary mark to detect cycles + std::size_t weight = 0; // number of proper direct or indirect bases + std::vector vtbl; + vptr_type* static_vptr; + + bool is_base_of(class_* other) const { + return std::find( + transitive_derived.begin(), transitive_derived.end(), + other) != transitive_derived.end(); + } + + auto vptr() const -> const vptr_type& { + return *static_vptr; + } + + auto type_id_begin() const { + return type_ids.begin(); + } + + auto type_id_end() const { + return type_ids.end(); + } + }; + + struct overrider { + detail::overrider_info* info = nullptr; + overrider* next = nullptr; + std::vector vp; + class_* covariant_return_type = nullptr; + std::uintptr_t pf; + std::size_t method_index, spec_index; + }; + + using bitvec = boost::dynamic_bitset<>; + + struct group { + std::vector classes; + bool has_concrete_classes{false}; + }; + + using group_map = std::map; + + struct method_report { + std::size_t cells = 0; + std::size_t not_implemented = 0; + std::size_t ambiguous = 0; + }; + + struct report : method_report {}; + + static void accumulate(const method_report& partial, report& total); + + struct method { + detail::method_info* info; + std::vector vp; + class_* covariant_return_type = nullptr; + std::vector specs; + std::vector slots; + std::vector strides; + std::vector dispatch_table; + // following two are dummies, when converting to a function pointer, we will + // get the corresponding pointer from method_info + overrider not_implemented; + vptr_type gv_dispatch_table = nullptr; + auto arity() const { + return vp.size(); + } + method_report report; + }; + + std::deque classes; + std::vector methods; + std::size_t class_mark = 0; + bool compilation_done = false; +}; + +template +trace_type& +operator<<(trace_type& trace, const generic_compiler::class_& cls) { + if constexpr (Policy::template has_facet) { + trace << type_name(cls.type_ids[0]); + } + + return trace; +} + +template class Container, typename... T> +trace_type& operator<<( + trace_type& trace, + Container& classes) { + if constexpr (Policy::template has_facet) { + trace << "("; + const char* sep = ""; + for (auto cls : classes) { + trace << sep << *cls; + sep = ", "; + } + + trace << ")"; + } + + return trace; +} + +struct spec_name { + spec_name( + const detail::generic_compiler::method& method, + const detail::generic_compiler::overrider* def) + : method(method), def(def) { + } + const detail::generic_compiler::method& method; + const detail::generic_compiler::overrider* def; +}; + +template +trace_type& operator<<(trace_type& trace, const spec_name& sn) { + if (sn.def == &sn.method.not_implemented) { + trace << "not implemented"; + } else { + trace << type_name(sn.def->info->type); + } + + return trace; +} + +} // namespace detail + +template +struct compiler : detail::generic_compiler { + using policy_type = Policy; + using type_index_type = decltype(Policy::type_index(0)); + + typename detail::aggregate_reports< + mp11::mp_list, typename Policy::facets>::type report; + + std::unordered_map class_map; + + compiler(); + + auto compile(); + auto initialize(); + void install_global_tables(); + + void resolve_static_type_ids(); + void augment_classes(); + void calculate_transitive_derived(class_& cls); + void augment_methods(); + void assign_slots(); + void assign_tree_slots(class_& cls, std::size_t base_slot); + void assign_lattice_slots(class_& cls); + void build_dispatch_tables(); + void build_dispatch_table( + method& m, std::size_t dim, + std::vector::const_iterator group, const bitvec& candidates, + bool concrete); + void write_global_data(); + void print(const method_report& report) const; + static void select_dominant_overriders( + std::vector& dominants, std::size_t& pick, + std::size_t& remaining); + static bool is_more_specific(const overrider* a, const overrider* b); + static bool is_base(const overrider* a, const overrider* b); + + static type_id static_type(type_id type) { + if constexpr (std::is_base_of_v< + policies::deferred_static_rtti, policies::rtti>) { + return reinterpret_cast(type)(); + } else { + return type; + } + } + + mutable detail::trace_type trace; + static constexpr bool trace_enabled = + Policy::template has_facet; + using indent = typename detail::trace_type::indent; +}; + +compiler() -> compiler; + +template +void compiler::install_global_tables() { + if (!compilation_done) { + abort(); + } + + write_global_data(); + + print(report); + ++trace << "Finished\n"; +} + +template +auto compiler::compile() { + resolve_static_type_ids(); + augment_classes(); + augment_methods(); + assign_slots(); + build_dispatch_tables(); + + compilation_done = true; + + return report; +} + +template +auto compiler::initialize() { + compile(); + install_global_tables(); + + return *this; +} + +template +compiler::compiler() { +} + +template +void compiler::resolve_static_type_ids() { + using namespace detail; + + auto resolve = [](type_id* p) { + auto pf = reinterpret_cast(*p); + *p = pf(); + }; + + if constexpr (std::is_base_of_v) { + for (auto& ci : Policy::classes) { + resolve(&ci.type); + + if (*ci.last_base == 0) { + for (auto& ti : range{ci.first_base, ci.last_base}) { + resolve(&ti); + } + + *ci.last_base = 1; + } + } + + for (auto& method : Policy::methods) { + for (auto& ti : range{method.vp_begin, method.vp_end}) { + if (*method.vp_end == 0) { + resolve(&ti); + *method.vp_end = 1; + } + + for (auto& overrider : method.specs) { + if (*overrider.vp_end == 0) { + for (auto& ti : + range{overrider.vp_begin, overrider.vp_end}) { + resolve(&ti); + } + + *overrider.vp_end = 1; + } + } + } + } + } +} + +template +void compiler::augment_classes() { + using namespace detail; + + // scope + { + ++trace << "Static class info:\n"; + + // The standard does not guarantee that there is exactly one + // type_info object per class. However, it guarantees that the + // type_index for a class has a unique value. + for (auto& cr : Policy::classes) { + { + indent _(trace); + ++trace << type_name(cr.type) << ": " + << range{cr.first_base, cr.last_base} << "\n"; + } + + auto& rtc = class_map[Policy::type_index(cr.type)]; + + if (rtc == nullptr) { + rtc = &classes.emplace_back(); + rtc->is_abstract = cr.is_abstract; + rtc->static_vptr = cr.static_vptr; + } + + // In the unlikely case that a class does have more than one + // associated ti*, collect them in a vector. We don't use an + // unordered_set because, again, this situation is highly + // unlikely, and, were it to occur, the number of distinct ti*s + // would probably be small. + if (std::find( + rtc->type_ids.begin(), rtc->type_ids.end(), cr.type) == + rtc->type_ids.end()) { + rtc->type_ids.push_back(cr.type); + } + } + } + + // All known classes now have exactly one associated class_* in the + // map. Collect the bases. + + for (auto& cr : Policy::classes) { + auto& rtc = class_map[Policy::type_index(cr.type)]; + + for (auto base_iter = cr.first_base; base_iter != cr.last_base; + ++base_iter) { + auto rtb = class_map[Policy::type_index(*base_iter)]; + + if (!rtb) { + unknown_class_error error; + error.type = *base_iter; + + if constexpr (Policy::template has_facet< + policies::error_handler>) { + Policy::error(error); + } + + abort(); + } + + if (rtc != rtb) { + // At compile time we collected the class as its own + // improper base, as per std::is_base_of. Eliminate that. + rtc->transitive_bases.push_back(rtb); + } + } + } + + // At this point bases may contain duplicates, and also indirect + // bases. Clean that up. + + std::size_t mark = ++class_mark; + + for (auto& rtc : classes) { + decltype(rtc.transitive_bases) bases; + mark = ++class_mark; + + for (auto rtb : rtc.transitive_bases) { + if (rtb->mark != mark) { + bases.push_back(rtb); + rtb->mark = mark; + } + } + + // Record the "weight" of the class, i.e. the total number of direct + // and indirect proper bases it has. + rtc.weight = bases.size(); + rtc.transitive_bases.swap(bases); + } + + for (auto& rtc : classes) { + // Sort base classes by weight. This ensures that a base class is + // never preceded by one if its own bases classes. + std::sort( + rtc.transitive_bases.begin(), rtc.transitive_bases.end(), + [](auto a, auto b) { return a->weight > b->weight; }); + mark = ++class_mark; + + // Collect the direct base classes. The first base is certainly a + // direct one. Remove *its* bases from the candidates, by marking + // them. Continue with the next base that is not marked. It is the + // next direct base. And so on... + + for (auto rtb : rtc.transitive_bases) { + if (rtb->mark == mark) { + continue; + } + + rtc.direct_bases.push_back(rtb); + + for (auto rtbb : rtb->transitive_bases) { + rtbb->mark = mark; + } + } + } + + for (auto& rtc : classes) { + for (auto rtb : rtc.direct_bases) { + rtb->direct_derived.push_back(&rtc); + } + } + + for (auto& rtc : classes) { + calculate_transitive_derived(rtc); + } + + if constexpr (trace_enabled) { + ++trace << "Inheritance lattice:\n"; + + for (auto& rtc : classes) { + indent _(trace); + ++trace << rtc << "\n"; + + { + indent _(trace); + ++trace << "bases: " << rtc.direct_bases << "\n"; + ++trace << "derived: " << rtc.direct_derived << "\n"; + ++trace << "covariant: " << rtc.transitive_derived << "\n"; + } + } + } +} + +template +void compiler::calculate_transitive_derived(class_& cls) { + if (!cls.transitive_derived.empty()) { + return; + } + + cls.transitive_derived.insert(&cls); + + for (auto derived : cls.direct_derived) { + if (derived->transitive_derived.empty()) { + calculate_transitive_derived(*derived); + } + + std::copy( + derived->transitive_derived.begin(), + derived->transitive_derived.end(), + std::inserter( + cls.transitive_derived, cls.transitive_derived.end())); + } +} + +template +void compiler::augment_methods() { + using namespace policies; + using namespace detail; + + methods.resize(Policy::methods.size()); + + ++trace << "Methods:\n"; + indent _(trace); + + auto meth_iter = methods.begin(); + + for (auto& meth_info : Policy::methods) { + ++trace << type_name(meth_info.method_type) << " " + << range{meth_info.vp_begin, meth_info.vp_end} << "\n"; + + indent _(trace); + + meth_iter->info = &meth_info; + meth_iter->vp.reserve(meth_info.arity()); + meth_iter->slots.resize(meth_info.arity()); + std::size_t param_index = 0; + + for (auto ti : range{meth_info.vp_begin, meth_info.vp_end}) { + auto class_ = class_map[Policy::type_index(ti)]; + if (!class_) { + ++trace << "unkown class " << ti << "(" << type_name(ti) + << ") for parameter #" << (param_index + 1) << "\n"; + unknown_class_error error; + error.type = ti; + + if constexpr (Policy::template has_facet) { + Policy::error(error); + } + + abort(); + } + parameter param = {&*meth_iter, param_index++}; + meth_iter->vp.push_back(class_); + } + + if (Policy::type_index(meth_info.return_type) != + Policy::type_index(Policy::template static_type())) { + auto covariant_return_iter = + class_map.find(Policy::type_index(meth_info.return_type)); + + if (covariant_return_iter != class_map.end()) { + meth_iter->covariant_return_type = + covariant_return_iter->second; + } + } + + // initialize the function pointer in the synthetic not_implemented + // overrider + const auto method_index = meth_iter - methods.begin(); + auto spec_size = meth_info.specs.size(); + meth_iter->not_implemented.pf = + reinterpret_cast(meth_iter->info->not_implemented); + meth_iter->not_implemented.method_index = method_index; + meth_iter->not_implemented.spec_index = spec_size; + + meth_iter->specs.resize(spec_size); + auto spec_iter = meth_iter->specs.begin(); + + for (auto& overrider_info : meth_info.specs) { + spec_iter->method_index = meth_iter - methods.begin(); + spec_iter->spec_index = spec_iter - meth_iter->specs.begin(); + + ++trace << type_name(overrider_info.type) << " (" + << overrider_info.pf << ")\n"; + spec_iter->info = &overrider_info; + spec_iter->vp.reserve(meth_info.arity()); + std::size_t param_index = 0; + + for (auto type : + range{overrider_info.vp_begin, overrider_info.vp_end}) { + indent _(trace); + auto class_ = class_map[Policy::type_index(type)]; + + if (!class_) { + ++trace << "unknown class error for *virtual* parameter #" + << (param_index + 1) << "\n"; + unknown_class_error error; + error.type = type; + + if constexpr (Policy::template has_facet) { + Policy::error(error); + } + + abort(); + } + spec_iter->pf = + reinterpret_cast(spec_iter->info->pf); + spec_iter->vp.push_back(class_); + } + + if (meth_iter->covariant_return_type) { + auto covariant_return_iter = class_map.find( + Policy::type_index(overrider_info.return_type)); + + if (covariant_return_iter != class_map.end()) { + spec_iter->covariant_return_type = + covariant_return_iter->second; + } else { + unknown_class_error error; + error.type = overrider_info.return_type; + + if constexpr (Policy::template has_facet) { + Policy::error(error); + } + + abort(); + } + } + + ++spec_iter; + } + + ++meth_iter; + } + + for (auto& method : methods) { + std::size_t param_index = 0; + + for (auto vp : method.vp) { + vp->used_by_vp.push_back({&method, param_index++}); + } + } +} + +template +void compiler::assign_slots() { + ++trace << "Allocating slots...\n"; + + { + indent _(trace); + + ++class_mark; + + for (auto& cls : classes) { + if (cls.direct_bases.size() == 0) { + if (std::find_if( + cls.transitive_derived.begin(), + cls.transitive_derived.end(), [](auto cls) { + return cls->direct_bases.size() > 1; + }) == cls.transitive_derived.end()) { + indent _(trace); + assign_tree_slots(cls, 0); + } else { + assign_lattice_slots(cls); + } + } + } + } + + ++trace << "Allocating MI v-tables...\n"; + + { + indent _(trace); + + for (auto& cls : classes) { + if (cls.used_slots.empty()) { + // not involved in multiple inheritance + continue; + } + + auto first_slot = cls.used_slots.find_first(); + cls.first_slot = + first_slot == boost::dynamic_bitset<>::npos ? 0 : first_slot; + cls.vtbl.resize(cls.used_slots.size() - cls.first_slot); + ++trace << cls << " vtbl: " << cls.first_slot << "-" + << cls.used_slots.size() << " slots " << cls.used_slots + << "\n"; + } + } +} + +template +void compiler::assign_tree_slots(class_& cls, std::size_t base_slot) { + auto next_slot = base_slot; + using namespace detail; + + for (const auto& mp : cls.used_by_vp) { + ++trace << " in " << cls << " for " + << type_name(mp.method->info->method_type) << " parameter " + << mp.param << ": " << next_slot << "\n"; + mp.method->slots[mp.param] = next_slot++; + } + + cls.first_slot = 0; + cls.vtbl.resize(next_slot); + + for (auto pd : cls.direct_derived) { + assign_tree_slots(*pd, next_slot); + } +} + +template +void compiler::assign_lattice_slots(class_& cls) { + using namespace detail; + + if (cls.mark == class_mark) { + return; + } + + cls.mark = class_mark; + + if (!cls.used_by_vp.empty()) { + for (const auto& mp : cls.used_by_vp) { + ++trace << " in " << cls << " for " + << type_name(mp.method->info->method_type) << " parameter " + << mp.param << "\n"; + + indent _(trace); + + ++trace << "reserved slots: " << cls.reserved_slots + << " used slots: " << cls.used_slots << "\n"; + + auto unavailable_slots = cls.used_slots; + detail::merge_into(cls.reserved_slots, unavailable_slots); + + ++trace << "unavailable slots: " << unavailable_slots << "\n"; + + std::size_t slot = 0; + + for (; slot < unavailable_slots.size(); ++slot) { + if (!unavailable_slots[slot]) { + break; + } + } + + ++trace << "first available slot: " << slot << "\n"; + + mp.method->slots[mp.param] = slot; + detail::set_bit(cls.used_slots, slot); + detail::set_bit(cls.reserved_slots, slot); + + { + ++trace << "reserve slots " << cls.used_slots << " in:\n"; + indent _(trace); + + for (auto base : cls.transitive_bases) { + ++trace << *base << "\n"; + detail::merge_into(cls.used_slots, base->reserved_slots); + } + } + + { + ++trace << "assign slots " << cls.used_slots << " in:\n"; + indent _(trace); + + for (auto covariant : cls.transitive_derived) { + if (&cls != covariant) { + ++trace << *covariant << "\n"; + detail::merge_into( + cls.used_slots, covariant->used_slots); + + for (auto base : covariant->transitive_bases) { + ++trace << *base << "\n"; + detail::merge_into( + cls.used_slots, base->reserved_slots); + } + } + } + } + } + } + + for (auto pd : cls.direct_derived) { + assign_lattice_slots(*pd); + } +} + +template +void compiler::build_dispatch_tables() { + using namespace detail; + + for (auto& m : methods) { + ++trace << "Building dispatch table for " + << type_name(m.info->method_type) << "\n"; + indent _(trace); + + auto dims = m.arity(); + + std::vector groups; + groups.resize(dims); + + { + std::size_t dim = 0; + + for (auto vp : m.vp) { + auto& dim_group = groups[dim]; + ++trace << "make groups for param #" << dim << ", class " << *vp + << "\n"; + indent _(trace); + + for (auto covariant_class : vp->transitive_derived) { + ++trace << "specs applicable to " << *covariant_class + << "\n"; + bitvec mask; + mask.resize(m.specs.size()); + + std::size_t group_index = 0; + indent _(trace); + + for (auto& spec : m.specs) { + if (spec.vp[dim]->transitive_derived.find( + covariant_class) != + spec.vp[dim]->transitive_derived.end()) { + ++trace << type_name(spec.info->type) << "\n"; + mask[group_index] = 1; + } + ++group_index; + } + + auto& group = dim_group[mask]; + group.classes.push_back(covariant_class); + group.has_concrete_classes = group.has_concrete_classes || + !covariant_class->is_abstract; + + ++trace << "-> mask: " << mask << "\n"; + } + + ++dim; + } + } + + { + std::size_t stride = 1; + m.strides.reserve(dims - 1); + + for (std::size_t dim = 1; dim < m.arity(); ++dim) { + stride *= groups[dim - 1].size(); + ++trace << " stride for dim " << dim << " = " << stride + << "\n"; + m.strides.push_back(stride); + } + } + + for (std::size_t dim = 0; dim < m.arity(); ++dim) { + indent _(trace); + std::size_t group_num = 0; + + for (auto& [mask, group] : groups[dim]) { + ++trace << "groups for dim " << dim << ":\n"; + indent _(trace); + ++trace << group_num << " mask " << mask << ":\n"; + + for (auto cls : group.classes) { + indent _(trace); + ++trace << type_name(cls->type_ids[0]) << "\n"; + auto& entry = cls->vtbl[m.slots[dim] - cls->first_slot]; + entry.method_index = &m - &methods[0]; + entry.vp_index = dim; + entry.group_index = group_num; + } + + ++group_num; + } + } + + { + ++trace << "building dispatch table\n"; + bitvec all(m.specs.size()); + all = ~all; + build_dispatch_table(m, dims - 1, groups.end() - 1, all, true); + + if (m.arity() > 1) { + indent _(trace); + m.report.cells = 1; + ++trace << "dispatch table rank: "; + const char* prefix = ""; + + for (const auto& dim_groups : groups) { + m.report.cells *= dim_groups.size(); + trace << prefix << dim_groups.size(); + prefix = " x "; + } + + prefix = ", concrete only: "; + + for (const auto& dim_groups : groups) { + auto cells = std::count_if( + dim_groups.begin(), dim_groups.end(), + [](const auto& group) { + return group.second.has_concrete_classes; + }); + trace << prefix << cells; + prefix = " x "; + } + + trace << "\n"; + } + + print(m.report); + accumulate(m.report, report); + } + } +} + +template +void compiler::build_dispatch_table( + method& m, std::size_t dim, + std::vector::const_iterator group_iter, const bitvec& candidates, + bool concrete) { + using namespace detail; + + indent _(trace); + std::size_t group_index = 0; + + for (const auto& [group_mask, group] : *group_iter) { + auto mask = candidates & group_mask; + + if constexpr (trace_enabled) { + ++trace << "group " << dim << "/" << group_index << " mask " << mask + << "\n"; + indent _(trace); + for (auto cls : range{group.classes.begin(), group.classes.end()}) { + ++trace << type_name(cls->type_ids[0]) << "\n"; + } + } + + if (dim == 0) { + std::vector candidates; + std::size_t i = 0; + + for (auto& spec : m.specs) { + if (mask[i]) { + candidates.push_back(&spec); + } + ++i; + } + + if constexpr (trace_enabled) { + ++trace << "select best of:\n"; + indent _(trace); + + for (auto& app : candidates) { + ++trace << "#" << app->spec_index << " " + << type_name(app->info->type) << "\n"; + } + } + + std::vector dominants = candidates; + std::size_t pick, remaining; + + select_dominant_overriders(dominants, pick, remaining); + + if (remaining == 0) { + indent _(trace); + ++trace << "not implemented\n"; + m.dispatch_table.push_back(&m.not_implemented); + ++m.report.not_implemented; + } else { + auto overrider = dominants[pick]; + m.dispatch_table.push_back(overrider); + ++trace; + + trace << "-> #" << overrider->spec_index << " " + << type_name(overrider->info->type) + << " pf = " << overrider->info->pf; + + if (remaining > 1) { + trace << " (ambiguous)"; + ++m.report.ambiguous; + } + + trace << "\n"; + + // ------------------------------------------------------------- + // next + + // First remove the dominant overriders from the candidates. + // Note that the dominants appear in the candidates in the same + // relative order. + auto candidate = candidates.begin(); + remaining = 0; + + for (auto dominant : dominants) { + if (*candidate == dominant) { + *candidate = nullptr; + } else { + ++remaining; + } + + ++candidate; + } + + if (remaining == 0) { + ++trace << "no 'next'\n"; + overrider->next = &m.not_implemented; + } else { + if constexpr (trace_enabled) { + ++trace << "for 'next', select best of:\n"; + indent _(trace); + + for (auto& app : candidates) { + if (app) { + ++trace << "#" << app->spec_index << " " + << type_name(app->info->type) << "\n"; + } + } + } + + select_dominant_overriders(candidates, pick, remaining); + + auto next_overrider = candidates[pick]; + overrider->next = next_overrider; + + ++trace << "-> #" << next_overrider->spec_index << " " + << type_name(next_overrider->info->type) + << " pf = " << next_overrider->info->pf; + + if (remaining > 1) { + trace << " (ambiguous)"; + // do not increment m.report.ambiguous, for same reason + } + + trace << "\n"; + } + } + } else { + build_dispatch_table( + m, dim - 1, group_iter - 1, mask, + concrete && group.has_concrete_classes); + } + + ++group_index; + } +} + +inline void detail::generic_compiler::accumulate( + const method_report& partial, report& total) { + total.cells += partial.cells; + total.not_implemented += partial.not_implemented != 0; + total.ambiguous += partial.ambiguous != 0; +} + +template +void compiler::write_global_data() { + using namespace policies; + using namespace detail; + + auto dispatch_data_size = std::accumulate( + methods.begin(), methods.end(), std::size_t(0), + [](auto sum, auto& m) { return sum + m.dispatch_table.size(); }); + dispatch_data_size = std::accumulate( + classes.begin(), classes.end(), dispatch_data_size, + [](auto sum, auto& cls) { return sum + cls.vtbl.size(); }); + + Policy::dispatch_data.resize(dispatch_data_size); + auto gv_first = Policy::dispatch_data.data(); + auto gv_last = gv_first + Policy::dispatch_data.size(); + auto gv_iter = gv_first; + + ++trace << "Initializing multi-method dispatch tables at " << gv_iter + << "\n"; + + for (auto& m : methods) { + if (m.info->arity() == 1) { + // Uni-methods just need an index in the method table. + m.info->slots_strides_ptr[0] = m.slots[0]; + } else { + auto strides_iter = std::copy( + m.slots.begin(), m.slots.end(), m.info->slots_strides_ptr); + std::copy(m.strides.begin(), m.strides.end(), strides_iter); + + if constexpr (trace_enabled) { + ++trace << rflush(4, Policy::dispatch_data.size()) << " " + << " method #" << m.dispatch_table[0]->method_index + << " " << type_name(m.info->method_type) << "\n"; + indent _(trace); + + for (auto& entry : m.dispatch_table) { + ++trace << "spec #" << entry->spec_index << " " + << spec_name(m, entry) << "\n"; + } + } + + m.gv_dispatch_table = gv_iter; + BOOST_ASSERT(gv_iter + m.dispatch_table.size() <= gv_last); + gv_iter = std::transform( + m.dispatch_table.begin(), m.dispatch_table.end(), gv_iter, + [](auto spec) { return spec->pf; }); + } + } + + ++trace << "Setting 'next' pointers\n"; + + for (auto& m : methods) { + indent _(trace); + ++trace << "method #" + << " " << type_name(m.info->method_type) << "\n"; + + for (auto& overrider : m.specs) { + if (overrider.next) { + ++trace << "#" << overrider.spec_index << " " + << spec_name(m, &overrider) << " -> "; + + trace << "#" << overrider.next->spec_index << " " + << spec_name(m, overrider.next); + *overrider.info->next = (void*)overrider.next->pf; + } else { + trace << "none"; + } + + trace << "\n"; + } + } + + ++trace << "Initializing v-tables at " << gv_iter << "\n"; + + for (auto& cls : classes) { + if (cls.first_slot == -1) { + // corner case: no methods for this class + *cls.static_vptr = gv_iter; + continue; + } + + *cls.static_vptr = gv_iter - cls.first_slot; + + ++trace << rflush(4, gv_iter - gv_first) << " " << gv_iter + << " vtbl for " << cls << " slots " << cls.first_slot << "-" + << (cls.first_slot + cls.vtbl.size() - 1) << "\n"; + indent _(trace); + + for (auto& entry : cls.vtbl) { + ++trace << "method #" << entry.method_index << " "; + auto& method = methods[entry.method_index]; + + if (method.arity() == 1) { + auto spec = method.dispatch_table[entry.group_index]; + trace << "spec #" << spec->spec_index << "\n"; + indent _(trace); + ++trace << type_name(method.info->method_type) << "\n"; + ++trace << spec_name(method, spec); + BOOST_ASSERT(gv_iter + 1 <= gv_last); + *gv_iter++ = spec->pf; + } else { + trace << "vp #" << entry.vp_index << " group #" + << entry.group_index << "\n"; + indent _(trace); + ++trace << type_name(method.info->method_type); + BOOST_ASSERT(gv_iter + 1 <= gv_last); + + if (entry.vp_index == 0) { + *gv_iter++ = std::uintptr_t( + method.gv_dispatch_table + entry.group_index); + } else { + *gv_iter++ = entry.group_index; + } + } + + trace << "\n"; + } + } + + ++trace << rflush(4, Policy::dispatch_data.size()) << " " << gv_iter + << " end\n"; + + if constexpr (Policy::template has_facet) { + Policy::register_vptrs(classes.begin(), classes.end()); + } +} + +template +void compiler::select_dominant_overriders( + std::vector& candidates, std::size_t& pick, + std::size_t& remaining) { + + pick = 0; + remaining = 0; + + for (size_t i = 0; i < candidates.size(); ++i) { + if (candidates[i]) { + for (size_t j = i + 1; j < candidates.size(); ++j) { + if (candidates[j]) { + if (is_more_specific(candidates[i], candidates[j])) { + candidates[j] = nullptr; + } else if (is_more_specific(candidates[j], candidates[i])) { + candidates[i] = nullptr; + break; // this one is dead + } + } + } + } + + if (candidates[i]) { + pick = i; + ++remaining; + } + } + + if (remaining <= 1 || !candidates[pick]->covariant_return_type) { + return; + } + + remaining = 0; + + for (size_t i = 0; i < candidates.size(); ++i) { + if (candidates[i]) { + for (size_t j = i + 1; j < candidates.size(); ++j) { + if (candidates[j]) { + BOOST_ASSERT(candidates[i] != candidates[j]); + + if (candidates[i]->covariant_return_type->is_base_of( + candidates[j]->covariant_return_type)) { + candidates[i] = nullptr; + } else if (candidates[j]->covariant_return_type->is_base_of( + candidates[i]->covariant_return_type)) { + candidates[j] = nullptr; + } + } + } + } + + if (candidates[i]) { + pick = i; + ++remaining; + } + } +} + +template +bool compiler::is_more_specific( + const overrider* a, const overrider* b) { + bool result = false; + + auto a_iter = a->vp.begin(), a_last = a->vp.end(), b_iter = b->vp.begin(); + + for (; a_iter != a_last; ++a_iter, ++b_iter) { + if (*a_iter != *b_iter) { + if ((*b_iter)->transitive_derived.find(*a_iter) != + (*b_iter)->transitive_derived.end()) { + result = true; + } else if ( + (*a_iter)->transitive_derived.find(*b_iter) != + (*a_iter)->transitive_derived.end()) { + return false; + } + } + } + + return result; +} + +template +bool compiler::is_base(const overrider* a, const overrider* b) { + bool result = false; + + auto a_iter = a->vp.begin(), a_last = a->vp.end(), b_iter = b->vp.begin(); + + for (; a_iter != a_last; ++a_iter, ++b_iter) { + if (*a_iter != *b_iter) { + if ((*a_iter)->transitive_derived.find(*b_iter) == + (*a_iter)->transitive_derived.end()) { + return false; + } else { + result = true; + } + } + } + + return result; +} + +template +void compiler::print(const method_report& report) const { + ++trace; + + if (report.cells) { + // only for multi-methods, uni-methods don't have dispatch tables + ++trace << report.cells << " dispatch table cells, "; + } + + trace << report.not_implemented << " not implemented, " << report.ambiguous + << " ambiguous\n"; +} + +template +auto initialize() -> compiler { + compiler compiler; + compiler.initialize(); + + return compiler; +} + +} // namespace openmethod +} // namespace boost + +#endif diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp new file mode 100644 index 0000000..aa1498d --- /dev/null +++ b/include/boost/openmethod/core.hpp @@ -0,0 +1,1323 @@ +// Copyright (c) 2018-2025 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) + +#ifndef BOOST_OPENMETHOD_CORE_HPP +#define BOOST_OPENMETHOD_CORE_HPP + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#ifndef BOOST_OPENMETHOD_DEFAULT_POLICY +#define BOOST_OPENMETHOD_DEFAULT_POLICY ::boost::openmethod::default_policy +#endif + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4646) +#endif + +namespace boost::openmethod { + +// ============================================================================= +// Registering classes + +namespace detail { + +template +auto collect_static_type_id() -> type_id { + if constexpr (std::is_base_of_v) { + return reinterpret_cast(Policy::template static_type); + } else { + return Policy::template static_type(); + } +} + +template +struct type_id_list; + +template +struct type_id_list, Policy> { + // If using deferred 'static_type', add an extra element in 'value', + // default-initialized to zero, indicating the ids need to be resolved. Set + // to 1 after this is done. + static constexpr std::size_t values = sizeof...(T) + + std::is_base_of_v; + static type_id value[values]; + static type_id* begin; + static type_id* end; +}; + +template +type_id type_id_list, Policy>::value[values] = { + collect_static_type_id()...}; + +template +type_id* type_id_list, Policy>::begin = value; + +template +type_id* type_id_list, Policy>::end = value + sizeof...(T); + +template +struct type_id_list, Policy> { + static constexpr type_id* const begin = nullptr; + static constexpr auto end = begin; +}; + +template +struct class_declaration_aux; + +template +struct class_declaration_aux> + : class_info { + class_declaration_aux() { + this->type = collect_static_type_id(); + this->first_base = type_id_list, Policy>::begin; + this->last_base = type_id_list, Policy>::end; + Policy::classes.push_back(*this); + this->is_abstract = std::is_abstract_v; + this->static_vptr = &Policy::template static_vptr; + } + + ~class_declaration_aux() { + Policy::classes.remove(*this); + } +}; + +// Collect the base classes of a list of classes. The result is a mp11 map that +// associates each class to a list starting with the class itself, followed by +// all its bases, as per std::is_base_of. Thus the list includes the class +// itself at least twice: at the front, and down the list, as its own improper +// base. The direct and indirect bases are all included. The runtime will +// extract the direct proper bases. +template +using inheritance_map = mp11::mp_list, mp11::mp_list>, + Cs>...>; + +// ============================================================================= +// Policy helpers + +template +constexpr bool is_policy = std::is_base_of_v; + +template +struct extract_policy; + +template<> +struct extract_policy<> { + using policy = BOOST_OPENMETHOD_DEFAULT_POLICY; + using others = mp11::mp_list<>; +}; + +template +struct extract_policy { + using policy = std::conditional_t< + is_policy, Type, BOOST_OPENMETHOD_DEFAULT_POLICY>; + using others = std::conditional_t< + is_policy, mp11::mp_list<>, mp11::mp_list>; +}; + +template +struct extract_policy { + static_assert(!is_policy, "policy must be the last in the list"); + using policy = typename extract_policy::policy; + using others = mp11::mp_push_front< + typename extract_policy::others, Type1>; +}; + +// ============================================================================= +// optimal_cast + +template +struct requires_dynamic_cast_ref_aux : std::true_type {}; + +template +struct requires_dynamic_cast_ref_aux< + B, D, std::void_t(std::declval()))>> + : std::false_type {}; + +template +constexpr bool requires_dynamic_cast = + detail::requires_dynamic_cast_ref_aux::value; + +template +auto optimal_cast(B&& obj) -> decltype(auto) { + if constexpr (requires_dynamic_cast) { + return Policy::template dynamic_cast_ref(std::forward(obj)); + } else { + return static_cast(obj); + } +} + +// ============================================================================= +// Common details + +template +struct is_virtual : std::false_type {}; + +template +struct is_virtual> : std::true_type {}; + +template +struct remove_virtual_aux { + using type = T; +}; + +template +struct remove_virtual_aux> { + using type = T; +}; + +template +using remove_virtual = typename remove_virtual_aux::type; + +template +using virtual_type = typename virtual_traits::virtual_type; + +template +using virtual_types = boost::mp11::mp_transform< + remove_virtual, boost::mp11::mp_filter>; + +template +struct parameter_traits { + static auto peek(const T& arg) { + return nullptr; + } + + template + static auto cast(T value) -> T { + return value; + } +}; + +template +struct parameter_traits, Policy> : virtual_traits {}; + +template +struct parameter_traits, Policy> + : virtual_traits, Policy> {}; + +template +struct parameter_traits&, Policy> + : virtual_traits&, Policy> {}; + +} // namespace detail + +// ============================================================================= +// virtual_traits + +template +struct virtual_traits { + using virtual_type = void; +}; + +template +struct virtual_traits { + using virtual_type = std::remove_cv_t; + + static auto peek(const T& arg) -> const T& { + return arg; + } + + template + static auto cast(T& obj) -> D& { + return detail::optimal_cast(obj); + } +}; + +template +struct virtual_traits { + using virtual_type = T; + + static auto peek(const T& arg) -> const T& { + return arg; + } + + template + static auto cast(T&& obj) -> D&& { + return detail::optimal_cast(obj); + } +}; + +// For covariant return types. +template +struct virtual_traits { + using virtual_type = std::remove_cv_t; +}; + +template +struct use_classes { + using tuple_type = boost::mp11::mp_apply< + std::tuple, + boost::mp11::mp_transform_q< + boost::mp11::mp_bind_front< + detail::class_declaration_aux, + typename detail::extract_policy::policy>, + boost::mp11::mp_apply< + detail::inheritance_map, + typename detail::extract_policy::others>>>; + tuple_type tuple; + static use_classes instance; +}; + +template +use_classes use_classes::instance; + +// ============================================================================= +// virtual_ptr + +namespace detail { + +template +struct is_virtual> : std::true_type {}; + +template +struct is_virtual&> : std::true_type {}; + +template +struct is_virtual_ptr_aux : std::false_type {}; + +template +struct is_virtual_ptr_aux> : std::true_type {}; + +template +struct is_virtual_ptr_aux&> : std::true_type { +}; + +template +constexpr bool is_virtual_ptr = detail::is_virtual_ptr_aux::value; + +template +class virtual_ptr_impl { + public: + using traits = virtual_traits; + using element_type = Class; + static constexpr bool is_smart_ptr = false; + + static constexpr bool use_indirect_vptrs = + Policy::template has_facet; + + template + virtual_ptr_impl(Other& other) + : obj(&other), vp(Policy::dynamic_vptr(other)) { + } + + template + virtual_ptr_impl(const virtual_ptr& other) + : obj(other.get()), vp(other.vp) { + } + + template + virtual_ptr_impl(virtual_ptr& other) + : obj(other.get()), vp(other.vp) { + // Why is this needed? Consider this conversion conversion from + // smart to dumb pointer: + // virtual_ptr> p = ...; + // virtual_ptr q = p; + // Since 'p' is not const, in the absence of this ctor, + // virtual_ptr_impl(Other&) would be preferred to + // virtual_ptr_impl(const virtual_ptr& other), and + // that is incorrect. + } + + template + virtual_ptr_impl(Other& other, vptr_type vp) : obj(&other), vp(vp) { + } + + Class* get() const { + return obj; + } + + auto operator->() const { + return obj; + } + + auto operator*() const -> element_type& { + return *obj; + } + + auto pointer() const -> const Class*& { + return obj; + } + + template + decltype(auto) cast() const { + static_assert( + std::is_base_of_v || std::is_base_of_v); + + return virtual_ptr( + traits::template cast(*obj), vp); + } + + template + friend struct virtual_traits; + + protected: + std::conditional_t vp; + Class* obj; +}; + +// SFINAE helper: defined only if Class and Other are both smart pointers of the +// same kind and that a Other* can be converted to a Class* (this takes +// inheritance and constness into account). +template< + class Class, class Other, class Policy, + class Rebind = typename virtual_traits::template rebind< + typename Other::element_type>, + class IsConvertible = typename std::is_convertible< + typename Other::element_type*, typename Class::element_type*>::type> +struct enable_if_compatible_smart_ptr; + +template +struct enable_if_compatible_smart_ptr< + Class, Other, Policy, Other, std::true_type> { + using type = void; // it doesn't matter which type, it's just for SFNIAE. +}; + +template +class virtual_ptr_impl< + Class, Policy, + std::void_t< + typename virtual_traits::template rebind>> { + public: + using traits = virtual_traits; + using element_type = typename Class::element_type; + + template + friend class virtual_ptr; + + template + friend struct virtual_traits; + + protected: + static constexpr bool use_indirect_vptrs = + Policy::template has_facet; + + std::conditional_t vp; + Class obj; + + public: + static constexpr bool is_smart_ptr = true; + + template< + class Other, + typename = + typename enable_if_compatible_smart_ptr::type> + virtual_ptr_impl(const Other& other) + : obj(other), vp(Policy::dynamic_vptr(*other)) { + } + + template< + class Other, + typename = + typename enable_if_compatible_smart_ptr::type> + virtual_ptr_impl(Other&& other) + : obj(std::move(other)), vp(Policy::dynamic_vptr(*other)) { + } + + template< + class Other, + typename = + typename enable_if_compatible_smart_ptr::type> + virtual_ptr_impl(virtual_ptr& other) + : obj(other.obj), vp(other.vp) { + } + + template< + class Other, + typename = + typename enable_if_compatible_smart_ptr::type> + virtual_ptr_impl(const virtual_ptr& other) + : obj(other.obj), vp(other.vp) { + } + + template< + class Other, + typename = + typename enable_if_compatible_smart_ptr::type> + virtual_ptr_impl(virtual_ptr&& other) + : obj(std::move(other.obj)), vp(other.vp) { + } + + auto get() const -> element_type* { + return obj.get(); + } + + auto operator->() const -> element_type* { + return get(); + } + + auto operator*() const -> element_type& { + return *get(); + } + + auto pointer() const -> const Class& { + return obj; + } + + template + virtual_ptr_impl(Arg&& obj, const vptr_type& vp) + : obj(std::forward(obj)), vp(vp) { + } + + template + decltype(auto) cast() & { + static_assert( + std::is_base_of_v || + std::is_base_of_v); + + using other_smart_ptr = typename traits::template rebind; + + return virtual_ptr( + traits::template cast(obj), vp); + } + + template + decltype(auto) cast() const& { + static_assert( + std::is_base_of_v || + std::is_base_of_v); + + using other_smart_ptr = typename traits::template rebind; + + return virtual_ptr( + traits::template cast(obj), vp); + } + + template + decltype(auto) cast() && { + static_assert( + std::is_base_of_v || + std::is_base_of_v); + + using other_smart_ptr = typename traits::template rebind; + + return virtual_ptr( + traits::template cast(std::move(obj)), vp); + } +}; + +} // namespace detail + +template +class virtual_ptr : public detail::virtual_ptr_impl { + using impl = detail::virtual_ptr_impl; + + public: + using detail::virtual_ptr_impl::virtual_ptr_impl; + using element_type = typename impl::element_type; + + template + friend class detail::virtual_ptr_impl; + + template + static auto final(Other&& obj) { + using other_traits = virtual_traits; + using other_class = typename other_traits::virtual_type; + + static_assert( + std::is_base_of_v || + std::is_base_of_v); + + if constexpr (Policy::template has_facet) { + // check that dynamic type == static type + auto static_type = Policy::template static_type(); + auto dynamic_type = Policy::dynamic_type(other_traits::peek(obj)); + + if (dynamic_type != static_type) { + type_mismatch_error error; + error.type = dynamic_type; + Policy::error(error); + abort(); + } + } + + return virtual_ptr( + std::forward(obj), + Policy::template static_vptr); + } + + auto vptr() const { + return this->vp; + } +}; + +template +virtual_ptr(Class&) -> virtual_ptr; + +template +inline auto final_virtual_ptr(Class&& obj) { + return virtual_ptr, Policy>::final( + std::forward(obj)); +} + +template +inline auto final_virtual_ptr(Class&& obj) { + return virtual_ptr>::final( + std::forward(obj)); +} + +template +bool operator==( + const virtual_ptr& left, + const virtual_ptr& right) { + return &*left == &*right; +} + +template +bool operator!=( + const virtual_ptr& left, + const virtual_ptr& right) { + return !(left == right); +} + +template +struct virtual_traits, Policy> { + using virtual_type = typename virtual_ptr::element_type; + + static auto peek(const virtual_ptr& ptr) + -> const virtual_ptr& { + return ptr; + } + + template + static decltype(auto) cast(const virtual_ptr& ptr) { + return ptr.template cast(); + } + + template + static decltype(auto) cast(virtual_ptr&& ptr) { + return std::move(ptr).template cast(); + } +}; + +template +struct virtual_traits&, Policy> { + using virtual_type = typename virtual_ptr::element_type; + + static auto peek(const virtual_ptr& ptr) + -> const virtual_ptr& { + return ptr; + } + + template + static decltype(auto) cast(const virtual_ptr& ptr) { + return ptr.template cast< + typename std::remove_reference_t::element_type>(); + } + + template + static decltype(auto) cast(virtual_ptr&& ptr) { + return std::move(ptr).template cast(); + } +}; + +// ============================================================================= +// with_vptr + +namespace detail { + +void boost_openmethod_policy(...); +void boost_openmethod_bases(...); + +template +using with_vptr_policy = + decltype(boost_openmethod_policy(std::declval())); + +template +struct update_vptr_bases; + +template +void update_vptr(Class* obj); + +template +struct update_vptr_bases> { + template + static void fn(Class* obj) { + (update_vptr(static_cast(obj)), ...); + } +}; + +template +void update_vptr(Class* obj) { + using policy = with_vptr_policy; + using bases = decltype(boost_openmethod_bases(obj)); + + if constexpr (mp11::mp_size::value == 0) { + if constexpr (policy::template has_facet) { + obj->boost_openmethod_vptr = &policy::template static_vptr; + } else { + obj->boost_openmethod_vptr = policy::template static_vptr; + } + } else { + update_vptr_bases::template fn(obj); + } +} + +struct with_vptr_derived {}; + +template +class with_vptr_aux; + +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic ignored "-Wnon-template-friend" +#endif + +template +class with_vptr_aux { + protected: + template + friend void update_vptr(Other*); + friend auto boost_openmethod_policy(Class*) -> Policy; + friend auto boost_openmethod_bases(Class*) -> mp11::mp_list<>; + + with_vptr_aux() { + (void)&use_classes::instance; + detail::update_vptr(static_cast(this)); + } + + ~with_vptr_aux() { + boost_openmethod_vptr = nullptr; + } + + friend auto boost_openmethod_vptr(const Class& obj) -> vptr_type { + if constexpr (Policy::template has_facet) { + return *obj.boost_openmethod_vptr; + } else { + return obj.boost_openmethod_vptr; + } + } + + friend auto boost_openmethod_policy(Class*) -> Policy; + + std::conditional_t< + Policy::template has_facet, const vptr_type*, + vptr_type> + boost_openmethod_vptr = nullptr; +}; + +template +class with_vptr_aux : with_vptr_derived { + protected: + friend void update_vptr(Class*); + + with_vptr_aux() { + (void)&use_classes>::instance; + detail::update_vptr(static_cast(this)); + } + + ~with_vptr_aux() { + detail::update_vptr( + static_cast(static_cast(this))); + } + + friend auto boost_openmethod_bases(Class*) -> mp11::mp_list; +}; + +} // namespace detail + +template +struct with_vptr; + +template +struct with_vptr + : detail::with_vptr_aux {}; + +template +struct with_vptr + : detail::with_vptr_aux> {}; + +template +class with_vptr : detail::with_vptr_derived { + + static_assert( + !detail::is_policy && !detail::is_policy && + (!detail::is_policy && ...), + "policy can be specified only for root classes"); + + protected: + with_vptr() { + (void)&use_classes< + Class, Base1, Base2, MoreBases..., + detail::with_vptr_policy>::instance; + detail::update_vptr(static_cast(this)); + } + + ~with_vptr() { + auto obj = static_cast(this); + detail::update_vptr(static_cast(obj)); + detail::update_vptr(static_cast(obj)); + (detail::update_vptr(static_cast(obj)), ...); + } + + friend auto boost_openmethod_policy(Class*) + -> detail::with_vptr_policy; + friend auto boost_openmethod_bases(Class*) + -> mp11::mp_list; + friend auto boost_openmethod_vptr(const Class& obj) -> vptr_type { + return boost_openmethod_vptr(static_cast(obj)); + } +}; + +// ============================================================================= +// Method + +namespace detail { + +template +struct select_overrider_virtual_type_aux { + using type = void; +}; + +template +struct select_overrider_virtual_type_aux, Q, Policy> { + using type = virtual_type; +}; + +template +struct select_overrider_virtual_type_aux< + virtual_ptr, virtual_ptr, Policy> { + using type = + typename virtual_traits, Policy>::virtual_type; +}; + +template +struct select_overrider_virtual_type_aux< + const virtual_ptr&, const virtual_ptr&, Policy> { + using type = typename virtual_traits< + const virtual_ptr&, Policy>::virtual_type; +}; + +template +using select_overrider_virtual_type = + typename select_overrider_virtual_type_aux::type; + +template +using overrider_virtual_types = boost::mp11::mp_remove< + boost::mp11::mp_transform_q< + boost::mp11::mp_bind_back, + MethodParameters, OverriderParameters>, + void>; + +template +struct static_offsets; + +template +struct has_static_offsets : std::false_type {}; + +template +struct has_static_offsets< + Method, std::void_t::slots)>> + : std::true_type {}; + +template +struct is_policy_compatible : std::true_type {}; + +template +struct is_policy_compatible> + : std::is_same {}; + +template +struct is_policy_compatible&> + : std::is_same {}; + +void boost_openmethod_vptr(...); + +template +constexpr bool has_vptr_fn = std::is_same_v< + decltype(boost_openmethod_vptr(std::declval())), vptr_type>; + +} // namespace detail + +template< + typename Method, typename ReturnType, + class Policy = BOOST_OPENMETHOD_DEFAULT_POLICY> +class method; + +template< + typename Name, typename... Parameters, typename ReturnType, class Policy> +class method + : public detail::method_info { + // Aliases used in implementation only. Everything extracted from template + // arguments is capitalized like the arguments themselves. + using DeclaredParameters = mp11::mp_list; + using CallParameters = + boost::mp11::mp_transform; + using VirtualParameters = + typename detail::virtual_types; + using Signature = auto(Parameters...) -> ReturnType; + using FunctionPointer = auto (*)(detail::remove_virtual...) + -> ReturnType; + static constexpr auto Arity = boost::mp11::mp_count_if< + mp11::mp_list, detail::is_virtual>::value; + + // sanity checks + static_assert(Arity > 0, "method must have at least one virtual argument"); + static_assert( + (true && ... && + detail::is_policy_compatible::value)); + + static std::size_t slots_strides[2 * Arity - 1]; + // Slots followed by strides. No stride for first virtual argument. + // For 1-method: the offset of the method in the method table, which + // contains a pointer to a function. + // For multi-methods: the offset of the first virtual argument in the + // method table, which contains a pointer to the corresponding cell in + // the dispatch table, followed by the offset of the second argument and + // the stride in the second dimension, etc. + + template + auto vptr(const ArgType& arg) const -> vptr_type; + + template + auto check_static_offset(std::size_t actual, std::size_t expected) const + -> void; + + template + auto resolve_uni(const ArgType& arg, const MoreArgTypes&... more_args) const + -> std::uintptr_t; + + template + auto resolve_multi_first( + const ArgType& arg, const MoreArgTypes&... more_args) const + -> std::uintptr_t; + + template< + std::size_t VirtualArg, typename MethodArgList, typename ArgType, + typename... MoreArgTypes> + auto resolve_multi_next( + vptr_type dispatch, const ArgType& arg, + const MoreArgTypes&... more_args) const -> std::uintptr_t; + + template + FunctionPointer resolve(const ArgType&... args) const; + + static BOOST_NORETURN auto + not_implemented_handler(detail::remove_virtual... args) + -> ReturnType; + + template + struct thunk; + + friend class generator; + + method(); + method(const method&) = delete; + method(method&&) = delete; + ~method(); + + public: + // Public aliases. + using name_type = Name; + using return_type = ReturnType; + using function_type = ReturnType (*)(detail::remove_virtual...); + + static method fn; + + auto operator()(detail::remove_virtual... args) const + -> ReturnType; + + template + static function_type next; + + private: + template< + auto Overrider, typename OverriderReturn, + typename... OverriderParameters> + struct thunk { + static auto fn(detail::remove_virtual... arg) -> ReturnType; + using OverriderParameterTypeIds = detail::type_id_list< + detail::overrider_virtual_types< + DeclaredParameters, mp11::mp_list, + Policy>, + Policy>; + }; + + template + struct override_impl { + explicit override_impl(FunctionPointer* next = nullptr); + }; + + template + struct override_aux; + + template + struct override_aux + : override_impl {}; + + template< + auto Function, class FnClass, typename FnReturnType, + typename... FnParameters> + struct override_aux { + static auto fn(FnClass* this_, FnParameters&&... args) -> FnReturnType { + return (this_->*Function)(std::forward(args)...); + } + + override_impl impl{&next}; + }; + + public: + template + struct override { + std::tuple...> impl; + }; +}; + +template< + typename Name, typename... Parameters, typename ReturnType, class Policy> +method + method::fn; + +template< + typename Name, typename... Parameters, typename ReturnType, class Policy> +template +typename method::FunctionPointer + method::next; + +template +constexpr bool is_method = std::is_base_of_v; + +template< + typename Name, typename... Parameters, typename ReturnType, class Policy> +method::method() { + method_info::slots_strides_ptr = slots_strides; + + using virtual_type_ids = detail::type_id_list< + boost::mp11::mp_transform_q< + boost::mp11::mp_bind_back, + VirtualParameters>, + Policy>; + method_info::vp_begin = virtual_type_ids::begin; + method_info::vp_end = virtual_type_ids::end; + method_info::not_implemented = (void*)not_implemented_handler; + method_info::method_type = Policy::template static_type(); + method_info::return_type = Policy::template static_type< + typename virtual_traits::virtual_type>(); + Policy::methods.push_back(*this); +} + +template< + typename Name, typename... Parameters, typename ReturnType, class Policy> +std::size_t method< + Name(Parameters...), ReturnType, Policy>::slots_strides[2 * Arity - 1]; + +template< + typename Name, typename... Parameters, typename ReturnType, class Policy> +method::~method() { + Policy::methods.remove(*this); +} + +template< + typename Name, typename... Parameters, typename ReturnType, class Policy> +template +auto method::check_static_offset( + std::size_t actual, std::size_t expected) const -> void { + using namespace detail; + + if (actual != expected) { + if (Policy::template has_facet) { + Error error; + error.method = Policy::template static_type(); + error.expected = this->slots_strides[0]; + error.actual = actual; + Policy::error(error); + + abort(); + } + } +} + +// ----------------------------------------------------------------------------- +// method dispatch + +template< + typename Name, typename... Parameters, typename ReturnType, class Policy> +BOOST_FORCEINLINE auto +method::operator()( + detail::remove_virtual... args) const -> ReturnType { + using namespace detail; + auto pf = resolve(parameter_traits::peek(args)...); + + return pf(std::forward>(args)...); +} + +template< + typename Name, typename... Parameters, typename ReturnType, class Policy> +template +BOOST_FORCEINLINE + typename method::FunctionPointer + method::resolve( + const ArgType&... args) const { + using namespace detail; + + std::uintptr_t pf; + + if constexpr (Arity == 1) { + pf = resolve_uni, ArgType...>(args...); + } else { + pf = resolve_multi_first, ArgType...>( + args...); + } + + return reinterpret_cast(pf); +} + +template< + typename Name, typename... Parameters, typename ReturnType, class Policy> +template +BOOST_FORCEINLINE auto +method::vptr(const ArgType& arg) const + -> vptr_type { + if constexpr (detail::is_virtual_ptr) { + return arg.vptr(); + } else if constexpr (detail::has_vptr_fn) { + return boost_openmethod_vptr(arg); + } else { + return Policy::dynamic_vptr(arg); + } +} + +template< + typename Name, typename... Parameters, typename ReturnType, class Policy> +template +BOOST_FORCEINLINE auto +method::resolve_uni( + const ArgType& arg, const MoreArgTypes&... more_args) const + -> std::uintptr_t { + + using namespace detail; + using namespace boost::mp11; + + if constexpr (is_virtual>::value) { + vptr_type vtbl; + + if constexpr (is_virtual_ptr) { + vtbl = arg.vptr(); + } else { + vtbl = vptr(arg); + } + + if constexpr (has_static_offsets::value) { + if constexpr (Policy::template has_facet< + policies::runtime_checks>) { + check_static_offset( + static_offsets::slots[0], this->slots_strides[0]); + } + return vtbl[static_offsets::slots[0]]; + } else { + return vtbl[this->slots_strides[0]]; + } + } else { + return resolve_uni>(more_args...); + } +} + +template< + typename Name, typename... Parameters, typename ReturnType, class Policy> +template +BOOST_FORCEINLINE auto +method::resolve_multi_first( + const ArgType& arg, const MoreArgTypes&... more_args) const + -> std::uintptr_t { + + using namespace detail; + using namespace boost::mp11; + + if constexpr (is_virtual>::value) { + vptr_type vtbl; + + if constexpr (is_virtual_ptr) { + vtbl = arg.vptr(); + } else { + vtbl = vptr(arg); + } + + std::size_t slot; + + if constexpr (has_static_offsets::value) { + slot = static_offsets::slots[0]; + if constexpr (Policy::template has_facet< + policies::runtime_checks>) { + check_static_offset( + static_offsets::slots[0], this->slots_strides[0]); + } + } else { + slot = this->slots_strides[0]; + } + + // The first virtual parameter is special. Since its stride is + // 1, there is no need to store it. Also, the method table + // contains a pointer into the multi-dimensional dispatch table, + // already resolved to the appropriate group. + auto dispatch = reinterpret_cast(vtbl[slot]); + return resolve_multi_next<1, mp_rest, MoreArgTypes...>( + dispatch, more_args...); + } else { + return resolve_multi_first, MoreArgTypes...>( + more_args...); + } +} + +template< + typename Name, typename... Parameters, typename ReturnType, class Policy> +template< + std::size_t VirtualArg, typename MethodArgList, typename ArgType, + typename... MoreArgTypes> +BOOST_FORCEINLINE auto +method::resolve_multi_next( + vptr_type dispatch, const ArgType& arg, + const MoreArgTypes&... more_args) const -> std::uintptr_t { + + using namespace detail; + using namespace boost::mp11; + + if constexpr (is_virtual>::value) { + vptr_type vtbl; + + if constexpr (is_virtual_ptr) { + vtbl = arg.vptr(); + } else { + vtbl = vptr(arg); + } + + std::size_t slot, stride; + + if constexpr (has_static_offsets::value) { + slot = static_offsets::slots[VirtualArg]; + stride = static_offsets::strides[VirtualArg - 1]; + if constexpr (Policy::template has_facet< + policies::runtime_checks>) { + check_static_offset( + this->slots_strides[VirtualArg], slot); + check_static_offset( + this->slots_strides[2 * VirtualArg], stride); + } + } else { + slot = this->slots_strides[VirtualArg]; + stride = this->slots_strides[Arity + VirtualArg - 1]; + } + + dispatch = dispatch + vtbl[slot] * stride; + } + + if constexpr (VirtualArg + 1 == Arity) { + return *dispatch; + } else { + return resolve_multi_next< + VirtualArg + 1, mp_rest, MoreArgTypes...>( + dispatch, more_args...); + } +} + +// ----------------------------------------------------------------------------- +// Error handling + +template< + typename Name, typename... Parameters, typename ReturnType, class Policy> +BOOST_NORETURN auto +method::not_implemented_handler( + detail::remove_virtual... args) -> ReturnType { + if constexpr (Policy::template has_facet) { + not_implemented_error error; + error.method = Policy::template static_type(); + error.arity = Arity; + type_id types[sizeof...(args)]; + auto ti_iter = types; + (..., + (*ti_iter++ = Policy::dynamic_type( + detail::parameter_traits::peek(args)))); + std::copy_n( + types, + (std::min)(sizeof...(args), not_implemented_error::max_types), + &error.types[0]); + Policy::error(error); + } + + abort(); // in case user handler "forgets" to abort +} + +// ----------------------------------------------------------------------------- +// thunk + +namespace detail { +template +constexpr bool is_virtual_ptr_compatible = + is_virtual_ptr == is_virtual_ptr; +} + +template< + typename Name, typename... Parameters, typename ReturnType, class Policy> +template< + auto Overrider, typename OverriderReturn, typename... OverriderParameters> +auto method:: + thunk::fn( + detail::remove_virtual... arg) -> ReturnType { + using namespace detail; + static_assert( + (true && ... && + is_virtual_ptr_compatible), + "virtual_ptr mismatch"); + return Overrider( + detail::parameter_traits::template cast< + OverriderParameters>( + std::forward>(arg))...); +} + +// ----------------------------------------------------------------------------- +// overriders + +template< + typename Name, typename... Parameters, typename ReturnType, class Policy> +template +method::override_impl< + Function, FnReturnType>::override_impl(FunctionPointer* p_next) { + using namespace detail; + + // Work around MSVC bug: using &next as a default value + // for 'next' confuses it about Parameters not being expanded. + if (!p_next) { + p_next = &next; + } + + static overrider_info info; + + if (info.method) { + BOOST_ASSERT(info.method == &fn); + return; + } + + info.method = &fn; + info.return_type = Policy::template static_type< + typename virtual_traits::virtual_type>(); + info.type = Policy::template static_type(); + info.next = reinterpret_cast(p_next); + using Thunk = thunk; + info.pf = (void*)Thunk::fn; + info.vp_begin = Thunk::OverriderParameterTypeIds::begin; + info.vp_end = Thunk::OverriderParameterTypeIds::end; + fn.specs.push_back(info); +} + +} // namespace boost::openmethod + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif diff --git a/include/boost/openmethod/decode.hpp b/include/boost/openmethod/decode.hpp new file mode 100644 index 0000000..2f97a0d --- /dev/null +++ b/include/boost/openmethod/decode.hpp @@ -0,0 +1,208 @@ +#ifndef BOOST_OPENMETHOD_DECODE_HPP +#define BOOST_OPENMETHOD_DECODE_HPP + +#include + +namespace boost { +namespace openmethod { + +constexpr std::uint16_t stop_bit = 1 << (sizeof(uint16_t) * 8 - 1); +constexpr std::uint16_t index_bit = stop_bit >> 1; + +template +void decode_dispatch_data(Data& init) { + using namespace boost::openmethod::detail; + + constexpr auto pointer_size = sizeof(std::uintptr_t); + + trace_type trace; + using indent = typename trace_type::indent; + + trace << "Decoding dispatch data for " + << type_name(Policy::template static_type()) << "\n"; + + auto method_count = 0, multi_method_count = 0; + + for (auto& method : Policy::methods) { + ++method_count; + + if (method.arity() >= 2) { + ++multi_method_count; + } + } + + ++trace << method_count << " methods, " << multi_method_count + << " multi-methods\n"; + + // First copy the slots and strides to the static arrays in methods. Also + // build an array of arrays of pointer to method definitions. Methods and + // definitions are in reverse order, because of how 'list' works. While + // building the array of array of defintions, we put them back in the order + // in which the compiler saw them. + auto packed_slots_iter = init.encoded.slots; + auto methods = (method_info**)alloca(method_count * pointer_size); + auto methods_iter = methods; + auto method_defs = (uintptr_t**)alloca(method_count * pointer_size); + auto method_defs_iter = method_defs; + auto dispatch_tables = + (std::uintptr_t**)alloca(method_count * pointer_size); + auto multi_method_to_method = + (std::size_t*)alloca(multi_method_count * sizeof(std::size_t)); + auto multi_method_to_method_iter = multi_method_to_method; + + { + auto method_index = 0; + + for (auto& method : Policy::methods) { + ++trace << "method " << type_name(method.method_type) << "\n"; + indent _(trace); + + *methods_iter++ = &method; + + ++trace << "specializations:\n"; + + for (auto& spec : method.specs) { + indent _(trace); + ++trace << spec.pf << " " << type_name(spec.type) << "\n"; + } + + auto slots_strides_count = 2 * method.arity() - 1; + + // copy slots and strides into the method's static + ++trace << "installing " << slots_strides_count + << " slots and strides\n"; + std::copy_n( + packed_slots_iter, slots_strides_count, + method.slots_strides_ptr); + packed_slots_iter += slots_strides_count; + + auto specs = + (uintptr_t*)alloca((method.specs.size() + 1) * pointer_size); + *method_defs_iter++ = specs; + ++trace << "specs index: " << specs << "\n"; + specs = std::transform( + method.specs.begin(), method.specs.end(), specs, + [](auto& spec) { return (uintptr_t)spec.pf; }); + *specs++ = (uintptr_t)method.not_implemented; + ++method_index; + } + } + + // Decode dispatch tables for multi-methods, in place, and keep track of + // them in an array. We will use it when we fill the vtables. + + ++trace << "decoding multi-method dispatch tables\n"; + { + std::size_t method_index = 0; + auto dtbl_iter = init.dtbls; + + for (auto& method : Policy::methods) { + // Resist the temptation to use 'continue' to skip uni-methods, as + // 'method_index' needs to be incremented. + if (method.arity() > 1) { + indent _(trace); + + dispatch_tables[method_index] = dtbl_iter; + ++trace << "multi-method " << method_index + << " dispatch table at " << dtbl_iter << "\n"; + + indent __(trace); + ++trace << "specs:"; + + auto defs = method_defs[method_index]; + bool more = true; + + while (more) { + more = !(*dtbl_iter & stop_bit); + auto spec_index = *dtbl_iter & ~stop_bit; + trace << " " << spec_index; + *dtbl_iter++ = defs[spec_index]; + }; + + trace << "\n"; + } + + ++method_index; + } + } + + ++trace << "decoding v-tables\n"; + + auto encode_iter = init.encoded.vtbls; + auto decode_iter = init.vtbls; + bool last; + + auto fetch = [&]() { + BOOST_ASSERT((char*)(encode_iter + 1) >= (char*)decode_iter); + auto code = *encode_iter++; + last = code & stop_bit; + return code & ~stop_bit; + }; + + for (auto& cls : Policy::classes) { + if (*cls.static_vptr != nullptr) { + continue; + } + + indent _1(trace); + ++trace << "class " << type_name(cls.type) << "\n"; + + indent _2(trace); + + auto first_slot = fetch(); + ++trace << "first slot: " << first_slot << "\n"; + + *cls.static_vptr = decode_iter - first_slot; + + do { + auto code = fetch(); + + if (code & index_bit) { + auto index = code & ~index_bit; + ++trace << "multi-method group " << index << "\n"; + *decode_iter++ = index; + } else { + auto method_index = code; + auto method = methods[method_index]; + auto group_index = fetch(); // spec or group + + if (method->arity() == 1) { + ++trace << "uni-method " << method_index << " spec " + << group_index; + *decode_iter++ = method_defs[method_index][group_index]; + } else { + ++trace << "multi-method " << method_index << " group " + << group_index; + indent _(trace); + trace << type_name(method->method_type); + *decode_iter++ = + (std::uintptr_t)(dispatch_tables[method_index] + + group_index); + } + + trace << "\n"; + indent _(trace); + ++trace << type_name(method->method_type) << "\n"; + } + } while (!last); + } + + ++trace << decode_iter << " " << encode_iter << "\n"; + + auto waste = sizeof(init.encoded) - sizeof(init.vtbls); + + if (waste > 0) { + ++trace << waste << " bytes wasted\n"; + } + + using namespace policies; + + if constexpr (Policy::template has_facet) { + Policy::register_vptrs(Policy::classes.begin(), Policy::classes.end()); + } +} + +} // namespace openmethod +} // namespace boost + +#endif diff --git a/include/boost/openmethod/detail/ostdstream.hpp b/include/boost/openmethod/detail/ostdstream.hpp new file mode 100644 index 0000000..a3dc709 --- /dev/null +++ b/include/boost/openmethod/detail/ostdstream.hpp @@ -0,0 +1,91 @@ +// Copyright (c) 2018-2025 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) + +#ifndef BOOST_OPENMETHOD_DETAIL_OSTDSTREAM_HPP +#define BOOST_OPENMETHOD_DETAIL_OSTDSTREAM_HPP + +#include +#include +#include +#include + +namespace boost { +namespace openmethod { +namespace detail { + +// ----------------------------------------------------------------------------- +// lightweight ostream + +struct ostdstream { + FILE* stream = nullptr; + + ostdstream(FILE* stream = nullptr) : stream(stream) { + } + + void on(FILE* stream = stderr) { + this->stream = stream; + } + + void off() { + this->stream = nullptr; + } + + bool is_on() const { + return stream != nullptr; + } +}; + +struct ostderr : ostdstream { + ostderr() : ostdstream(stderr) { + } +}; + +inline ostdstream cerr; + +inline ostdstream& operator<<(ostdstream& os, const char* str) { + if (os.stream) { + fputs(str, os.stream); + } + + return os; +} + +inline ostdstream& operator<<(ostdstream& os, const std::string_view& view) { + if (os.stream) { + fwrite(view.data(), sizeof(*view.data()), view.length(), os.stream); + } + + return os; +} + +inline ostdstream& operator<<(ostdstream& os, const void* value) { + if (os.stream) { + std::array str; + auto end = std::to_chars( + str.data(), str.data() + str.size(), + reinterpret_cast(value), 16) + .ptr; + os << std::string_view(str.data(), end - str.data()); + } + + return os; +} + +inline ostdstream& operator<<(ostdstream& os, std::size_t value) { + if (os.stream) { + std::array str; + auto end = + std::to_chars(str.data(), str.data() + str.size(), value).ptr; + os << std::string_view(str.data(), end - str.data()); + } + + return os; +} + +} // namespace detail +} // namespace openmethod +} // namespace boost + +#endif diff --git a/include/boost/openmethod/detail/static_list.hpp b/include/boost/openmethod/detail/static_list.hpp new file mode 100644 index 0000000..55e864b --- /dev/null +++ b/include/boost/openmethod/detail/static_list.hpp @@ -0,0 +1,215 @@ +// Copyright (c) 2018-2025 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) + +#ifndef BOOST_OPENMETHOD_DETAIL_STATIC_LIST_HPP +#define BOOST_OPENMETHOD_DETAIL_STATIC_LIST_HPP + +#include +#include + +namespace boost { +namespace openmethod { +namespace detail { + +template +class static_list { + public: + static_list(static_list&) = delete; + static_list() = default; + + class static_link { + public: + static_link(const static_link&) = delete; + static_link() = default; + + T* next() { + return next_ptr; + } + + protected: + friend class static_list; + T* prev_ptr; + T* next_ptr; + }; + + void push_back(T& node) { + BOOST_ASSERT(node.prev_ptr == nullptr); + BOOST_ASSERT(node.next_ptr == nullptr); + + if (!first) { + first = &node; + node.prev_ptr = &node; + return; + } + + auto last = first->prev_ptr; + last->next_ptr = &node; + node.prev_ptr = last; + first->prev_ptr = &node; + } + + void remove(T& node) { + BOOST_ASSERT(first != nullptr); + + auto prev = node.prev_ptr; + auto next = node.next_ptr; + auto last = first->prev_ptr; + + node.prev_ptr = nullptr; + node.next_ptr = nullptr; + + if (&node == last) { + if (&node == first) { + first = nullptr; + return; + } + + first->prev_ptr = prev; + prev->next_ptr = nullptr; + return; + } + + if (&node == first) { + first = next; + first->prev_ptr = last; + return; + } + + prev->next_ptr = next; + next->prev_ptr = prev; + } + + void clear() { + auto next = first; + first = nullptr; + + while (next) { + auto cur = next; + next = cur->next_ptr; + cur->prev_ptr = nullptr; + cur->next_ptr = nullptr; + } + } + + class iterator { + public: + using iterator_category = std::forward_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = T; + using pointer = value_type*; + using reference = value_type&; + + iterator() : ptr(nullptr) { + } + explicit iterator(T* p) : ptr(p) { + } + + reference operator*() { + return *ptr; + } + pointer operator->() { + return ptr; + } + + iterator& operator++() { + BOOST_ASSERT(ptr); + ptr = ptr->next_ptr; + return *this; + } + + iterator operator++(int) { + auto tmp = *this; + ++(*this); + return tmp; + } + + friend bool operator==(const iterator& a, const iterator& b) { + return a.ptr == b.ptr; + }; + friend bool operator!=(const iterator& a, const iterator& b) { + return a.ptr != b.ptr; + }; + + private: + T* ptr; + }; + + iterator begin() { + return iterator(first); + } + + iterator end() { + return iterator(nullptr); + } + + class const_iterator { + public: + using iterator_category = std::forward_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = const T; + using pointer = value_type*; + using reference = value_type&; + + const_iterator() : ptr(nullptr) { + } + explicit const_iterator(T* p) : ptr(p) { + } + + reference operator*() { + return *ptr; + } + pointer operator->() { + return ptr; + } + + const_iterator& operator++() { + BOOST_ASSERT(ptr); + ptr = ptr->next_ptr; + return *this; + } + + const_iterator operator++(int) { + auto tmp = *this; + ++(*this); + return tmp; + } + + friend bool + operator==(const const_iterator& a, const const_iterator& b) { + return a.ptr == b.ptr; + }; + friend bool + operator!=(const const_iterator& a, const const_iterator& b) { + return a.ptr != b.ptr; + }; + + private: + T* ptr; + }; + + const_iterator begin() const { + return const_iterator(first); + } + + const_iterator end() const { + return const_iterator(nullptr); + } + + std::size_t size() const { + return std::distance(begin(), end()); + } + + bool empty() const { + return !first; + } + + protected: + T* first; +}; + +} // namespace detail +} // namespace openmethod +} // namespace boost +#endif diff --git a/include/boost/openmethod/detail/trace.hpp b/include/boost/openmethod/detail/trace.hpp new file mode 100644 index 0000000..4bb445b --- /dev/null +++ b/include/boost/openmethod/detail/trace.hpp @@ -0,0 +1,163 @@ +// Copyright (c) 2018-2025 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) + +#ifndef BOOST_OPENMETHOD_DETAIL_TRACE_HPP +#define BOOST_OPENMETHOD_DETAIL_TRACE_HPP + +#include + +#include + +namespace boost { +namespace openmethod { +namespace detail { + +template +struct range; + +struct rflush { + std::size_t width; + std::size_t value; + explicit rflush(std::size_t width, std::size_t value) + : width(width), value(value) { + } +}; + +struct type_name { + type_name(type_id type) : type(type) { + } + type_id type; +}; + +template +struct trace_type { + static constexpr bool trace_enabled = + Policy::template has_facet; + + std::size_t indentation_level{0}; + + trace_type& operator++() { + if constexpr (trace_enabled) { + if (Policy::trace_enabled) { + for (int i = 0; i < indentation_level; ++i) { + Policy::trace_stream << " "; + } + } + } + + return *this; + } + + struct indent { + trace_type& trace; + int by; + + explicit indent(trace_type& trace, int by = 2) : trace(trace), by(by) { + trace.indentation_level += by; + } + + ~indent() { + trace.indentation_level -= by; + } + }; +}; + +template +auto& write_range(trace_type& trace, range range, F fn) { + if constexpr (trace_type::trace_enabled) { + if (Policy::trace_enabled) { + trace << "("; + const char* sep = ""; + for (auto value : range) { + trace << sep << fn(value); + sep = ", "; + } + + trace << ")"; + } + } + + return trace; +} + +template +auto& operator<<(trace_type& trace, const T& value) { + if constexpr (trace_type::trace_enabled) { + if (Policy::trace_enabled) { + Policy::trace_stream << value; + } + } + return trace; +} + +template +auto& operator<<(trace_type& trace, const rflush& rf) { + if constexpr (trace_type::trace_enabled) { + if (Policy::trace_enabled) { + auto pad = rf.width; + auto remain = rf.value; + + int digits = 1; + auto tmp = rf.value / 10; + + while (tmp) { + ++digits; + tmp /= 10; + } + + while (digits < rf.width) { + trace << " "; + ++digits; + } + + trace << rf.value; + } + } + + return trace; +} + +template +auto& operator<<( + trace_type& trace, const boost::dynamic_bitset<>& bits) { + if constexpr (trace_type::trace_enabled) { + if (Policy::trace_enabled) { + if (Policy::trace_enabled) { + auto i = bits.size(); + while (i != 0) { + --i; + Policy::trace_stream << bits[i]; + } + } + } + } + + return trace; +} + +template +auto& operator<<(trace_type& trace, const range& tips) { + return write_range(trace, tips, [](auto tip) { return type_name(tip); }); +} + +template +auto& operator<<(trace_type& trace, const range& range) { + return write_range(trace, range, [](auto value) { return value; }); +} + +template +auto& operator<<(trace_type& trace, const type_name& manip) { + if constexpr (Policy::template has_facet) { + Policy::type_name(manip.type, trace); + } + + return trace; +} + +} // namespace detail +} // namespace openmethod +} // namespace boost + +#endif // BOOST_OPENMETHOD_DETAIL_HPP diff --git a/include/boost/openmethod/detail/types.hpp b/include/boost/openmethod/detail/types.hpp new file mode 100644 index 0000000..6999e9c --- /dev/null +++ b/include/boost/openmethod/detail/types.hpp @@ -0,0 +1,140 @@ +// Copyright (c) 2018-2025 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) + +#ifndef BOOST_OPENMETHOD_DETAIL_TYPES_HPP +#define BOOST_OPENMETHOD_DETAIL_TYPES_HPP + +#include + +#include + +namespace boost { +namespace openmethod { + +using type_id = std::uintptr_t; +using vptr_type = const std::uintptr_t*; + +template +struct virtual_; + +template +class virtual_ptr; + +template +struct virtual_traits; + +// ----------------------------------------------------------------------------- +// Error handling + +struct openmethod_error {}; + +struct not_implemented_error : openmethod_error { + type_id method; + std::size_t arity; + static constexpr std::size_t max_types = 16; + type_id types[max_types]; +}; + +struct unknown_class_error : openmethod_error { + type_id type; +}; + +struct hash_search_error : openmethod_error { + std::size_t attempts; + std::size_t buckets; +}; + +struct type_mismatch_error : openmethod_error { + type_id type; +}; + +struct static_offset_error : openmethod_error { + type_id method; + int actual, expected; +}; + +struct static_slot_error : static_offset_error {}; +struct static_stride_error : static_offset_error {}; + +namespace detail { + +template +struct types; + +template +struct range { + range(Iterator first, Iterator last) : first(first), last(last) { + } + + Iterator first, last; + + auto begin() const -> Iterator { + return first; + } + + auto end() const -> Iterator { + return last; + } +}; + +// ----------------------------------------------------------------------------- +// class info + +struct class_info : static_list::static_link { + type_id type; + vptr_type* static_vptr; + type_id *first_base, *last_base; + bool is_abstract{false}; + + auto vptr() const { + return static_vptr; + } + + auto type_id_begin() const { + return &type; + } + + auto type_id_end() const { + return &type + 1; + } +}; + +// ----------- +// method info + +struct overrider_info; + +struct method_info : static_list::static_link { + type_id* vp_begin; + type_id* vp_end; + static_list specs; + void* not_implemented; + type_id method_type; + type_id return_type; + std::size_t* slots_strides_ptr; + + auto arity() const { + return std::distance(vp_begin, vp_end); + } +}; + +struct overrider_info : static_list::static_link { + ~overrider_info() { + method->specs.remove(*this); + } + + method_info* method; // for the destructor, to remove definition + type_id return_type; // for N2216 disambiguation + type_id type; // of the function, for trace + void** next; + type_id *vp_begin, *vp_end; + void* pf; +}; + +} // namespace detail +} // namespace openmethod +} // namespace boost + +#endif diff --git a/include/boost/openmethod/macros.hpp b/include/boost/openmethod/macros.hpp new file mode 100644 index 0000000..5449970 --- /dev/null +++ b/include/boost/openmethod/macros.hpp @@ -0,0 +1,104 @@ +// Copyright (c) 2018-2025 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) + +#ifndef BOOST_OPENMETHOD_MACROS_HPP +#define BOOST_OPENMETHOD_MACROS_HPP + +#include +#include + +#include + +#define BOOST_OPENMETHOD_OVERRIDERS(NAME) \ + BOOST_PP_CAT(BOOST_OPENMETHOD_NAME(NAME), _overriders) + +namespace boost::openmethod::detail { + +template +struct enable_forwarder; + +template +struct enable_forwarder< + std::void_t()...))>, Method, + ReturnType, Parameters...> { + using type = ReturnType; +}; + +} // namespace boost::openmethod::detail + +#define BOOST_OPENMETHOD(NAME, ARGS, ...) \ + struct BOOST_OPENMETHOD_NAME(NAME); \ + template \ + typename ::boost::openmethod::detail::enable_forwarder< \ + void, \ + ::boost::openmethod::method< \ + BOOST_OPENMETHOD_NAME(NAME) ARGS, __VA_ARGS__>, \ + typename ::boost::openmethod::method< \ + BOOST_OPENMETHOD_NAME(NAME) ARGS, __VA_ARGS__>, \ + ForwarderParameters...>::type \ + BOOST_PP_CAT(BOOST_OPENMETHOD_NAME(NAME), _guide)( \ + ForwarderParameters && ... args); \ + template \ + inline typename ::boost::openmethod::detail::enable_forwarder< \ + void, \ + ::boost::openmethod::method< \ + BOOST_OPENMETHOD_NAME(NAME) ARGS, __VA_ARGS__>, \ + typename ::boost::openmethod::method< \ + BOOST_OPENMETHOD_NAME(NAME) ARGS, __VA_ARGS__>::return_type, \ + ForwarderParameters...>::type \ + NAME(ForwarderParameters&&... args) { \ + return ::boost::openmethod:: \ + method::fn( \ + std::forward(args)...); \ + } + +#define BOOST_OPENMETHOD_DETAIL_LOCATE_METHOD(NAME, ARGS) \ + template \ + struct boost_openmethod_detail_locate_method_aux; \ + template \ + struct boost_openmethod_detail_locate_method_aux { \ + using type = decltype(NAME##_guide(std::declval()...)); \ + }; + +#define BOOST_OPENMETHOD_DETAIL_OVERRIDE(INLINE, OVERRIDERS, NAME, ARGS, ...) \ + template \ + struct OVERRIDERS; \ + template<> \ + struct OVERRIDERS<__VA_ARGS__ ARGS> { \ + BOOST_OPENMETHOD_DETAIL_LOCATE_METHOD(NAME, ARGS); \ + static auto fn ARGS->__VA_ARGS__; \ + static auto has_next() -> bool { \ + using method_type = \ + boost_openmethod_detail_locate_method_aux::type; \ + return method_type::next != method_type::fn.not_implemented; \ + } \ + template \ + static decltype(auto) next(Args&&... args) { \ + BOOST_ASSERT(has_next()); \ + return boost_openmethod_detail_locate_method_aux< \ + void ARGS>::type::next(std::forward(args)...); \ + } \ + }; \ + INLINE BOOST_OPENMETHOD_REGISTER( \ + OVERRIDERS<__VA_ARGS__ ARGS>:: \ + boost_openmethod_detail_locate_method_aux< \ + void ARGS>::type::override::fn>); \ + INLINE auto OVERRIDERS<__VA_ARGS__ ARGS>::fn ARGS \ + ->boost::mp11::mp_back> + +#define BOOST_OPENMETHOD_INLINE_OVERRIDE(NAME, ARGS, ...) \ + BOOST_OPENMETHOD_DETAIL_OVERRIDE( \ + inline, BOOST_OPENMETHOD_OVERRIDERS(NAME), \ + BOOST_OPENMETHOD_NAME(NAME), ARGS, __VA_ARGS__) + +#define BOOST_OPENMETHOD_OVERRIDE(NAME, ARGS, ...) \ + BOOST_OPENMETHOD_DETAIL_OVERRIDE( \ + , BOOST_OPENMETHOD_OVERRIDERS(NAME), BOOST_OPENMETHOD_NAME(NAME), \ + ARGS, __VA_ARGS__) + +#define BOOST_OPENMETHOD_CLASSES(...) \ + BOOST_OPENMETHOD_REGISTER(::boost::openmethod::use_classes<__VA_ARGS__>); + +#endif diff --git a/include/boost/openmethod/macros/gensym.hpp b/include/boost/openmethod/macros/gensym.hpp new file mode 100644 index 0000000..7cef6b7 --- /dev/null +++ b/include/boost/openmethod/macros/gensym.hpp @@ -0,0 +1,7 @@ +#ifndef BOOST_OPENMETHOD_GENSYM + +#include + +#define BOOST_OPENMETHOD_GENSYM BOOST_PP_CAT(openmethod_gensym_, __COUNTER__) + +#endif diff --git a/include/boost/openmethod/macros/name.hpp b/include/boost/openmethod/macros/name.hpp new file mode 100644 index 0000000..6fc9399 --- /dev/null +++ b/include/boost/openmethod/macros/name.hpp @@ -0,0 +1,8 @@ +// Copyright (c) 2018-2025 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) + +#ifndef BOOST_OPENMETHOD_NAME +#define BOOST_OPENMETHOD_NAME(NAME) NAME##_boost_openmethod +#endif diff --git a/include/boost/openmethod/macros/register.hpp b/include/boost/openmethod/macros/register.hpp new file mode 100644 index 0000000..152bf45 --- /dev/null +++ b/include/boost/openmethod/macros/register.hpp @@ -0,0 +1,8 @@ +#ifndef BOOST_OPENMETHOD_GENSYM + +#include + +#define BOOST_OPENMETHOD_REGISTER(...) \ + static __VA_ARGS__ BOOST_OPENMETHOD_GENSYM + +#endif diff --git a/include/boost/openmethod/policies.hpp b/include/boost/openmethod/policies.hpp new file mode 100644 index 0000000..c26eeed --- /dev/null +++ b/include/boost/openmethod/policies.hpp @@ -0,0 +1,40 @@ +// Copyright (c) 2018-2025 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) + +#ifndef BOOST_OPENMETHOD_POLICY_HPP +#define BOOST_OPENMETHOD_POLICY_HPP + +#include + +#include +#include +#include +#include +#include +#include + +namespace boost::openmethod { + +namespace policies { + +struct release : basic_policy< + release, std_rtti, fast_perfect_hash, + vptr_vector, vectored_error_handler> {}; + +struct debug : release::add< + runtime_checks, basic_error_output, + basic_trace_output> {}; + +} // namespace policies + +#ifdef NDEBUG +using default_policy = policies::release; +#else +using default_policy = policies::debug; +#endif + +} // namespace boost::openmethod + +#endif diff --git a/include/boost/openmethod/policies/basic_error_output.hpp b/include/boost/openmethod/policies/basic_error_output.hpp new file mode 100644 index 0000000..7dafea0 --- /dev/null +++ b/include/boost/openmethod/policies/basic_error_output.hpp @@ -0,0 +1,28 @@ +// Copyright (c) 2018-2025 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) + +#ifndef BOOST_OPENMETHOD_POLICY_BASIC_ERROR_OUTPUT_HPP +#define BOOST_OPENMETHOD_POLICY_BASIC_ERROR_OUTPUT_HPP + +#include +#include + +namespace boost { +namespace openmethod { +namespace policies { + +template +struct basic_error_output : virtual error_output { + static Stream error_stream; +}; + +template +Stream basic_error_output::error_stream; + +} // namespace policies +} // namespace openmethod +} // namespace boost + +#endif diff --git a/include/boost/openmethod/policies/basic_policy.hpp b/include/boost/openmethod/policies/basic_policy.hpp new file mode 100644 index 0000000..169623f --- /dev/null +++ b/include/boost/openmethod/policies/basic_policy.hpp @@ -0,0 +1,141 @@ +// Copyright (c) 2018-2025 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) + +#ifndef BOOST_OPENMETHOD_POLICIES_CORE_HPP +#define BOOST_OPENMETHOD_POLICIES_CORE_HPP + +#include + +#include +#include + +#include +#include + +namespace boost { +namespace openmethod { +namespace detail { + +using class_catalog = detail::static_list; +using method_catalog = detail::static_list; + +} // namespace detail + +namespace policies { + +struct abstract_policy {}; + +// ----------------------------------------------------------------------------- +// Facets + +struct rtti { + static auto type_index(type_id type) -> type_id { + return type; + } + + template + static void type_name(type_id type, Stream& stream) { + stream << "type_id(" << type << ")"; + } +}; + +struct deferred_static_rtti : virtual rtti {}; + +struct error_handler {}; +struct indirect_vptr {}; +struct type_hash {}; +struct vptr {}; +struct extern_vptr : vptr {}; +struct error_output {}; +struct trace_output {}; +struct runtime_checks {}; + +// ----------------------------------------------------------------------------- +// Facet implementations + +struct debug; +struct release; + +// ----------------------------------------------------------------------------- +// domain + +template +struct domain { + static detail::class_catalog classes; + static detail::method_catalog methods; + template + static vptr_type static_vptr; + static std::vector dispatch_data; +}; + +template +detail::class_catalog domain::classes; + +template +detail::method_catalog domain::methods; + +template +template +vptr_type domain::static_vptr; + +template +std::vector domain::dispatch_data; + +template +struct rebind_facet { + using type = Facet; +}; + +template< + typename NewPolicy, typename OldPolicy, + template class GenericFacet, typename... Args> +struct rebind_facet> { + using type = GenericFacet; +}; + +template +struct basic_policy : virtual abstract_policy, + virtual domain, + virtual Facets... { + using facets = mp11::mp_list; + + template + static constexpr bool has_facet = std::is_base_of_v; + + template + using fork = basic_policy< + NewPolicy, typename rebind_facet::type...>; + + template + using add = basic_policy; + + template + using replace = boost::mp11::mp_apply< + basic_policy, + boost::mp11::mp_push_front< + boost::mp11::mp_replace_if_q< + facets, + boost::mp11::mp_bind_front_q< + boost::mp11::mp_quote_trait, Base>, + Facet>, + Policy>>; + + template + using remove = boost::mp11::mp_apply< + basic_policy, + boost::mp11::mp_push_front< + boost::mp11::mp_remove_if_q< + facets, + boost::mp11::mp_bind_front_q< + boost::mp11::mp_quote_trait, Base>>, + Policy>>; +}; + +} // namespace policies + +} // namespace openmethod +} // namespace boost + +#endif diff --git a/include/boost/openmethod/policies/basic_trace_output.hpp b/include/boost/openmethod/policies/basic_trace_output.hpp new file mode 100644 index 0000000..d9863da --- /dev/null +++ b/include/boost/openmethod/policies/basic_trace_output.hpp @@ -0,0 +1,45 @@ + +// Copyright (c) 2018-2025 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) + +#ifndef BOOST_OPENMETHOD_POLICY_BASIC_TRACE_OUTPUT_HPP +#define BOOST_OPENMETHOD_POLICY_BASIC_TRACE_OUTPUT_HPP + +#include +#include + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4996) +#endif + +namespace boost { +namespace openmethod { +namespace policies { + +template +struct basic_trace_output : virtual trace_output { + static bool trace_enabled; + static Stream trace_stream; +}; + +template +Stream basic_trace_output::trace_stream; + +template +bool basic_trace_output::trace_enabled([]() { + auto env = getenv("BOOST_OPENMETHOD_TRACE"); + return env && *env++ == '1' && *env++ == 0; +}()); + +} // namespace policies +} // namespace openmethod +} // namespace boost + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif diff --git a/include/boost/openmethod/policies/fast_perfect_hash.hpp b/include/boost/openmethod/policies/fast_perfect_hash.hpp new file mode 100644 index 0000000..bde16d7 --- /dev/null +++ b/include/boost/openmethod/policies/fast_perfect_hash.hpp @@ -0,0 +1,184 @@ +// Copyright (c) 2018-2025 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) + +#ifndef BOOST_OPENMETHOD_POLICY_FAST_PERFECT_HASH_HPP +#define BOOST_OPENMETHOD_POLICY_FAST_PERFECT_HASH_HPP + +#include + +#include + +namespace boost { +namespace openmethod { +namespace policies { + +template +class fast_perfect_hash : type_hash { + + static type_id hash_mult; + static std::size_t hash_shift; + static std::size_t hash_min; + static std::size_t hash_max; + static std::vector control; + + static void check(std::size_t index, type_id type); + + public: + struct report { + std::size_t first, last; + }; + + BOOST_FORCEINLINE + static auto hash_type_id(type_id type) -> type_id { + auto index = (hash_mult * type) >> hash_shift; + + if constexpr (Policy::template has_facet) { + check(index, type); + } + + return index; + } + + template + static report hash_initialize(ForwardIterator first, ForwardIterator last) { + if constexpr (Policy::template has_facet) { + hash_initialize(first, last, control); + } else { + std::vector buckets; + hash_initialize(first, last, buckets); + } + + return {hash_min, hash_max}; + } + + template + static void hash_initialize( + ForwardIterator first, ForwardIterator last, + std::vector& buckets); +}; + +template +template +void fast_perfect_hash::hash_initialize( + ForwardIterator first, ForwardIterator last, + std::vector& buckets) { + using namespace policies; + + constexpr bool trace_enabled = Policy::template has_facet; + const auto N = std::distance(first, last); + + if constexpr (trace_enabled) { + if (Policy::trace_enabled) { + Policy::trace_stream << "Finding hash factor for " << N + << " types\n"; + } + } + + std::default_random_engine rnd(13081963); + std::size_t total_attempts = 0; + std::size_t M = 1; + + for (auto size = N * 5 / 4; size >>= 1;) { + ++M; + } + + std::uniform_int_distribution uniform_dist; + + for (std::size_t pass = 0; pass < 4; ++pass, ++M) { + hash_shift = 8 * sizeof(type_id) - M; + auto hash_size = 1 << M; + + if constexpr (trace_enabled) { + if (Policy::trace_enabled) { + Policy::trace_stream << " trying with M = " << M << ", " + << hash_size << " buckets\n"; + } + } + + bool found = false; + std::size_t attempts = 0; + buckets.resize(hash_size); + + while (!found && attempts < 100000) { + std::fill(buckets.begin(), buckets.end(), static_cast(-1)); + ++attempts; + ++total_attempts; + found = true; + hash_mult = uniform_dist(rnd) | 1; + + for (auto iter = first; iter != last; ++iter) { + for (auto type_iter = iter->type_id_begin(); + type_iter != iter->type_id_end(); ++type_iter) { + auto type = *type_iter; + auto index = (type * hash_mult) >> hash_shift; + hash_min = (std::min)(hash_min, index); + hash_max = (std::max)(hash_max, index); + + if (buckets[index] != static_cast(-1)) { + found = false; + break; + } + + buckets[index] = type; + } + } + } + + if (found) { + ++hash_max; + + if constexpr (trace_enabled) { + if (Policy::trace_enabled) { + Policy::trace_stream << " found " << hash_mult << " after " + << total_attempts + << " attempts; span = [" << hash_min + << "," << hash_max << ")\n"; + } + } + + return; + } + } + + hash_search_error error; + error.attempts = total_attempts; + error.buckets = std::size_t(1) << M; + + if constexpr (Policy::template has_facet) { + Policy::error(error); + } + + abort(); +} + +template +void fast_perfect_hash::check(std::size_t index, type_id type) { + if (index < hash_min || index >= hash_max || control[index] != type) { + if constexpr (Policy::template has_facet) { + unknown_class_error error; + error.type = type; + Policy::error(error); + } + + abort(); + } +} + +template +type_id fast_perfect_hash::hash_mult; +template +std::size_t fast_perfect_hash::hash_shift; +template +std::size_t fast_perfect_hash::hash_min; +template +std::size_t fast_perfect_hash::hash_max; +template +std::vector fast_perfect_hash::control; + +} // namespace policies +} // namespace openmethod +} // namespace boost + +#endif diff --git a/include/boost/openmethod/policies/minimal_rtti.hpp b/include/boost/openmethod/policies/minimal_rtti.hpp new file mode 100644 index 0000000..e7cc32f --- /dev/null +++ b/include/boost/openmethod/policies/minimal_rtti.hpp @@ -0,0 +1,27 @@ +// Copyright (c) 2018-2025 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) + +#ifndef BOOST_OPENMETHOD_POLICY_MINIMAL_RTTI_HPP +#define BOOST_OPENMETHOD_POLICY_MINIMAL_RTTI_HPP + +#include + +namespace boost { +namespace openmethod { +namespace policies { + +struct minimal_rtti : virtual rtti { + template + static auto static_type() -> type_id { + static char id; + return reinterpret_cast(&id); + } +}; + +} // namespace policies +} // namespace openmethod +} // namespace boost + +#endif diff --git a/include/boost/openmethod/policies/std_rtti.hpp b/include/boost/openmethod/policies/std_rtti.hpp new file mode 100644 index 0000000..74f2139 --- /dev/null +++ b/include/boost/openmethod/policies/std_rtti.hpp @@ -0,0 +1,56 @@ +// Copyright (c) 2018-2025 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) + +#ifndef BOOST_OPENMETHOD_POLICY_STD_RTTI_HPP +#define BOOST_OPENMETHOD_POLICY_STD_RTTI_HPP + +#include + +#ifndef BOOST_NO_RTTI +#include +#include +#include +#endif + +namespace boost { +namespace openmethod { +namespace policies { + +struct std_rtti : virtual rtti { +#ifndef BOOST_NO_RTTI + template + static auto static_type() -> type_id { + auto tip = &typeid(Class); + return reinterpret_cast(tip); + } + + template + static auto dynamic_type(const Class& obj) -> type_id { + auto tip = &typeid(obj); + return reinterpret_cast(tip); + } + + template + static auto type_name(type_id type, Stream& stream) -> void { + stream << boost::core::demangle( + reinterpret_cast(type)->name()); + } + + static auto type_index(type_id type) -> std::type_index { + return std::type_index(*reinterpret_cast(type)); + } + + template + static auto dynamic_cast_ref(B&& obj) -> D { + return dynamic_cast(obj); + } +#endif +}; + +} // namespace policies +} // namespace openmethod +} // namespace boost + +#endif diff --git a/include/boost/openmethod/policies/throw_error_handler.hpp b/include/boost/openmethod/policies/throw_error_handler.hpp new file mode 100644 index 0000000..17d24cd --- /dev/null +++ b/include/boost/openmethod/policies/throw_error_handler.hpp @@ -0,0 +1,22 @@ +// Copyright (c) 2018-2025 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) + +#ifndef BOOST_OPENMETHOD_POLICY_THROW_ERROR_HPP +#define BOOST_OPENMETHOD_POLICY_THROW_ERROR_HPP + +#include + +namespace boost::openmethod::policies { + +struct throw_error_handler : error_handler { + template + [[noreturn]] static auto error(const Error& error) -> void { + throw error; + } +}; + +} // namespace boost::openmethod::policies + +#endif diff --git a/include/boost/openmethod/policies/vectored_error_handler.hpp b/include/boost/openmethod/policies/vectored_error_handler.hpp new file mode 100644 index 0000000..d58a500 --- /dev/null +++ b/include/boost/openmethod/policies/vectored_error_handler.hpp @@ -0,0 +1,90 @@ +// Copyright (c) 2018-2025 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) + +#ifndef BOOST_OPENMETHOD_POLICY_VECTORED_ERROR_HPP +#define BOOST_OPENMETHOD_POLICY_VECTORED_ERROR_HPP + +#include + +#include +#include + +namespace boost { +namespace openmethod { +namespace policies { + +template +class vectored_error_handler : public error_handler { + public: + using error_variant = std::variant< + openmethod_error, not_implemented_error, unknown_class_error, + hash_search_error, type_mismatch_error, static_slot_error, + static_stride_error>; + + using function_type = std::function; + + template + static auto error(const Error& error) -> void { + fn(error_variant(error)); + } + + static auto set_error_handler(function_type handler) -> function_type { + auto prev = fn; + fn = handler; + + return prev; + } + + private: + static function_type fn; + + static auto default_handler(const error_variant& error_v) { + using namespace detail; + using namespace policies; + + if constexpr (Policy::template has_facet) { + if (auto error = std::get_if(&error_v)) { + Policy::error_stream << "no applicable overrider for "; + Policy::type_name(error->method, Policy::error_stream); + Policy::error_stream << "("; + auto comma = ""; + + for (auto ti : + range{error->types, error->types + error->arity}) { + Policy::error_stream << comma; + Policy::type_name(ti, Policy::error_stream); + comma = ", "; + } + + Policy::error_stream << ")\n"; + } else if ( + auto error = std::get_if(&error_v)) { + Policy::error_stream << "unknown class "; + Policy::type_name(error->type, Policy::error_stream); + Policy::error_stream << "\n"; + } else if ( + auto error = std::get_if(&error_v)) { + Policy::error_stream << "invalid method table for "; + Policy::type_name(error->type, Policy::error_stream); + Policy::error_stream << "\n"; + } else if (auto error = std::get_if(&error_v)) { + Policy::error_stream << "could not find hash factors after " + << error->attempts << "s using " + << error->buckets << " buckets\n"; + } + } + } +}; + +template +typename vectored_error_handler::function_type + vectored_error_handler::fn = + vectored_error_handler::default_handler; + +} // namespace policies +} // namespace openmethod +} // namespace boost + +#endif diff --git a/include/boost/openmethod/policies/vptr_map.hpp b/include/boost/openmethod/policies/vptr_map.hpp new file mode 100644 index 0000000..d371ee8 --- /dev/null +++ b/include/boost/openmethod/policies/vptr_map.hpp @@ -0,0 +1,88 @@ + +// Copyright (c) 2018-2025 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) + +#ifndef BOOST_OPENMETHOD_POLICY_VPTR_MAP_HPP +#define BOOST_OPENMETHOD_POLICY_VPTR_MAP_HPP + +#include + +#include + +namespace boost { +namespace openmethod { +namespace policies { + +template< + class Policy, typename UseIndirectVptrs = void, + class Map = std::unordered_map< + type_id, + std::conditional_t< + std::is_same_v, const vptr_type*, + vptr_type>>> +class vptr_map : extern_vptr { + static_assert( + std::is_same_v || + std::is_same_v); + static constexpr bool use_indirect_vptrs = + std::is_same_v; + static_assert( + std::is_same_v || + std::is_same_v); + static_assert( + std::is_same_v == + use_indirect_vptrs); + + static Map vptrs; + + public: + template + static void register_vptrs(ForwardIterator first, ForwardIterator last) { + for (auto iter = first; iter != last; ++iter) { + for (auto type_iter = iter->type_id_begin(); + type_iter != iter->type_id_end(); ++type_iter) { + + if constexpr (use_indirect_vptrs) { + vptrs[*type_iter] = &iter->vptr(); + } else { + vptrs[*type_iter] = iter->vptr(); + } + } + } + } + + template + static auto dynamic_vptr(const Class& arg) -> const vptr_type& { + auto type = Policy::dynamic_type(arg); + auto iter = vptrs.find(type); + + if constexpr (Policy::template has_facet) { + if (iter == vptrs.end()) { + if constexpr (Policy::template has_facet) { + unknown_class_error error; + error.type = type; + Policy::error(error); + } + + abort(); + } + } + + if constexpr (use_indirect_vptrs) { + return *iter->second; + } else { + return iter->second; + } + } +}; + +template +Map vptr_map::vptrs; + +} // namespace policies +} // namespace openmethod +} // namespace boost + +#endif diff --git a/include/boost/openmethod/policies/vptr_vector.hpp b/include/boost/openmethod/policies/vptr_vector.hpp new file mode 100644 index 0000000..49d5d0a --- /dev/null +++ b/include/boost/openmethod/policies/vptr_vector.hpp @@ -0,0 +1,97 @@ +// Copyright (c) 2018-2025 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) + +#ifndef BOOST_OPENMETHOD_POLICY_VPTR_VECTOR_HPP +#define BOOST_OPENMETHOD_POLICY_VPTR_VECTOR_HPP + +#include + +#include +#include + +namespace boost { +namespace openmethod { +namespace policies { + +template +class vptr_vector : extern_vptr { + static_assert( + std::is_same_v || + std::is_same_v); + static constexpr bool use_indirect_vptrs = + std::is_same_v; + using element_type = + std::conditional_t; + static std::vector vptrs; + + public: + template + static auto register_vptrs(ForwardIterator first, ForwardIterator last) + -> void { + using namespace policies; + + std::size_t size; + + if constexpr (Policy::template has_facet) { + auto report = Policy::hash_initialize(first, last); + size = report.last; + } else { + size = 0; + + for (auto iter = first; iter != last; ++iter) { + for (auto type_iter = iter->type_id_begin(); + type_iter != iter->type_id_end(); ++type_iter) { + size = (std::max)(size, *type_iter); + } + } + + ++size; + } + + vptrs.resize(size); + + for (auto iter = first; iter != last; ++iter) { + for (auto type_iter = iter->type_id_begin(); + type_iter != iter->type_id_end(); ++type_iter) { + auto index = *type_iter; + + if constexpr (Policy::template has_facet) { + index = Policy::hash_type_id(index); + } + + if constexpr (use_indirect_vptrs) { + vptrs[index] = &iter->vptr(); + } else { + vptrs[index] = iter->vptr(); + } + } + } + } + + template + static auto dynamic_vptr(const Class& arg) -> const vptr_type& { + auto index = Policy::dynamic_type(arg); + + if constexpr (Policy::template has_facet) { + index = Policy::hash_type_id(index); + } + + if constexpr (use_indirect_vptrs) { + return *vptrs[index]; + } else { + return vptrs[index]; + } + } +}; + +template +std::vector::element_type> + vptr_vector::vptrs; + +} // namespace policies +} // namespace openmethod +} // namespace boost + +#endif diff --git a/include/boost/openmethod/shared_ptr.hpp b/include/boost/openmethod/shared_ptr.hpp new file mode 100644 index 0000000..c758c5c --- /dev/null +++ b/include/boost/openmethod/shared_ptr.hpp @@ -0,0 +1,129 @@ +// Copyright (c) 2018-2025 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) + +#ifndef BOOST_OPENMETHOD_SHARED_PTR_HPP +#define BOOST_OPENMETHOD_SHARED_PTR_HPP + +#include +#include + +namespace boost { +namespace openmethod { +namespace detail { + +template +struct shared_ptr_traits { + static const bool is_shared_ptr = false; +}; + +template +struct shared_ptr_traits> { + static const bool is_shared_ptr = true; + static const bool is_const_ref = false; + using virtual_type = Class; +}; + +template +struct shared_ptr_traits&> { + static const bool is_shared_ptr = true; + static const bool is_const_ref = true; + using virtual_type = Class; +}; + +} // namespace detail + +template +struct virtual_traits&, Policy> { + using virtual_type = std::remove_cv_t; + + static auto peek(const std::shared_ptr& arg) -> const Class& { + return *arg; + } + + template + using rebind = std::shared_ptr; + + template + static void check_cast() { + using namespace boost::openmethod::detail; + + static_assert(shared_ptr_traits::is_shared_ptr); + static_assert( + shared_ptr_traits::is_const_ref, + "cannot cast from 'const shared_ptr&' to " + "'shared_ptr'"); + static_assert( + std::is_class_v::virtual_type>); + } + + template + static auto cast(const std::shared_ptr& obj) { + using namespace boost::openmethod::detail; + + check_cast(); + + if constexpr (detail::requires_dynamic_cast) { + return std::dynamic_pointer_cast< + typename shared_ptr_traits::virtual_type>(obj); + } else { + return std::static_pointer_cast< + typename shared_ptr_traits::virtual_type>(obj); + } + } +}; + +template +struct virtual_traits, Policy> { + using virtual_type = std::remove_cv_t; + + static auto peek(const std::shared_ptr& arg) -> const Class& { + return *arg; + } + + template + using rebind = std::shared_ptr; + + template + static void check_cast() { + using namespace boost::openmethod::detail; + + static_assert(shared_ptr_traits::is_shared_ptr); + static_assert( + !shared_ptr_traits::is_const_ref, + "cannot cast from 'const shared_ptr&' to " + "'shared_ptr'"); + static_assert( + std::is_class_v::virtual_type>); + } + template + static auto cast(const std::shared_ptr& obj) { + using namespace boost::openmethod::detail; + + check_cast(); + + if constexpr (detail::requires_dynamic_cast) { + return std::dynamic_pointer_cast< + typename shared_ptr_traits::virtual_type>(obj); + } else { + return std::static_pointer_cast< + typename shared_ptr_traits::virtual_type>(obj); + } + } +}; + +template +using shared_virtual_ptr = virtual_ptr, Policy>; + +template< + class Class, class Policy = BOOST_OPENMETHOD_DEFAULT_POLICY, typename... T> +inline auto make_shared_virtual(T&&... args) { + return shared_virtual_ptr::final( + std::make_shared(std::forward(args)...)); +} + +} // namespace openmethod +} // namespace boost + +#endif diff --git a/include/boost/openmethod/unique_ptr.hpp b/include/boost/openmethod/unique_ptr.hpp new file mode 100644 index 0000000..37a82f2 --- /dev/null +++ b/include/boost/openmethod/unique_ptr.hpp @@ -0,0 +1,52 @@ +// Copyright (c) 2018-2025 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) + +#ifndef BOOST_OPENMETHOD_UNIQUE_PTR_HPP +#define BOOST_OPENMETHOD_UNIQUE_PTR_HPP + +#include + +#include + +namespace boost { +namespace openmethod { + +template +struct virtual_traits, Policy> { + using virtual_type = std::remove_cv_t; + + static auto peek(const std::unique_ptr& arg) -> const Class& { + return *arg; + } + + template + using rebind = std::unique_ptr; + + template + static auto cast(std::unique_ptr&& ptr) { + if constexpr (detail::requires_dynamic_cast) { + return Other( + &dynamic_cast(*ptr.release())); + } else { + return Other( + &static_cast(*ptr.release())); + } + } +}; + +template +using unique_virtual_ptr = virtual_ptr, Policy>; + +template< + class Class, class Policy = BOOST_OPENMETHOD_DEFAULT_POLICY, typename... T> +inline auto make_unique_virtual(T&&... args) { + return unique_virtual_ptr::final( + std::make_unique(std::forward(args)...)); +} + +} // namespace openmethod +} // namespace boost + +#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..c454bcb --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright (c) 2018-2025 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) + +file(GLOB test_cpp_files "test_*.cpp") +foreach(test_cpp ${test_cpp_files}) + cmake_path(REMOVE_EXTENSION test_cpp LAST_ONLY OUTPUT_VARIABLE test) + string(REGEX REPLACE ".*/" "" test ${test}) + add_executable(${test} ${test_cpp}) + target_link_libraries(${test} Boost::openmethod) + add_test(NAME ${test} COMMAND ${test}) +endforeach() diff --git a/test/Jamfile b/test/Jamfile new file mode 100644 index 0000000..52f2fda --- /dev/null +++ b/test/Jamfile @@ -0,0 +1,40 @@ +# Boost.OpenMethod Library Test Jamfile +# +# Copyright 2015-2019 Peter Dimov +# +# 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 + +import testing ; +import ../../config/checks/config : requires ; + +project + : requirements + + [ requires cxx11_variadic_templates cxx11_template_aliases cxx11_decltype cxx11_hdr_tuple ] + + extra + + msvc:on + gcc:on + clang:on + ; + +# list +run test_blackbox.cpp ; +run test_compiler.cpp ; +run test_core.cpp ; +run test_custom_rtti.cpp ; +run test_member_method.cpp ; +run test_namespaces.cpp ; +run test_pointer_to_method.cpp ; +run test_rolex.cpp ; +run test_static_list.cpp ; +run test_virtual_ptr_all.cpp ; +run test_virtual_ptr_basic.cpp ; +run test_virtual_ptr_state.cpp ; + +# quick (for CI) +alias quick : blackbox ; +explicit quick ; diff --git a/test/test_blackbox.cpp b/test/test_blackbox.cpp new file mode 100644 index 0000000..c2f56a9 --- /dev/null +++ b/test/test_blackbox.cpp @@ -0,0 +1,654 @@ +// Copyright (c) 2018-2025 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) + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "test_util.hpp" + +#define BOOST_TEST_MODULE openmethod +#include + +using namespace boost::openmethod; + +namespace animals { + +struct Animal { + explicit Animal(std::string str) { + name = std::move(str); + } + + Animal(const Animal&) = delete; + + Animal(Animal&&) = default; + + virtual ~Animal() { + } + + std::string name; +}; + +struct Property { + std::string owner = "Bill"; +}; + +struct Dog : Property, Animal { + using Animal::Animal; +}; + +struct Cat : Property, virtual Animal { + using Animal::Animal; +}; + +} // namespace animals + +// ----------------------------------------------------------------------------- +// pass virtual args by lvalue references + +namespace TEST_NS { + +using policy = test_policy_<__COUNTER__>; +using namespace animals; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat, policy); + +BOOST_OPENMETHOD(name, (virtual_), std::string, policy); + +BOOST_OPENMETHOD_OVERRIDE(name, (const Cat& cat), std::string) { + return cat.owner + "'s cat " + cat.name; +} + +BOOST_OPENMETHOD_OVERRIDE(name, (const Dog& dog), std::string) { + return dog.owner + "'s dog " + dog.name; +} + +BOOST_AUTO_TEST_CASE(cast_args_lvalue_refs) { + initialize(); + + Dog spot("Spot"); + BOOST_TEST(name(spot) == "Bill's dog Spot"); + + Cat felix("Felix"); + BOOST_TEST(name(felix) == "Bill's cat Felix"); +} +} // namespace TEST_NS + +namespace TEST_NS { + +using policy = test_policy_<__COUNTER__>; +using namespace animals; + +} // namespace TEST_NS + +// ----------------------------------------------------------------------------- +// pass virtual args by rvalue references + +namespace TEST_NS { + +using policy = test_policy_<__COUNTER__>; +using namespace animals; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat, policy); + +BOOST_OPENMETHOD( + teleport, (virtual_), std::unique_ptr, policy); + +BOOST_OPENMETHOD_OVERRIDE(teleport, (Cat && cat), std::unique_ptr) { + return std::make_unique(std::move(cat)); +} + +BOOST_OPENMETHOD_OVERRIDE(teleport, (Dog && dog), std::unique_ptr) { + return std::make_unique(std::move(dog)); +} + +BOOST_AUTO_TEST_CASE(cast_args_rvalue_refs) { + initialize(); + + { + Dog spot("Spot"); + auto animal = teleport(std::move(spot)); + BOOST_TEST(animal->name == "Spot"); + BOOST_TEST(spot.name == ""); + } + + { + Cat felix("Felix"); + auto animal = teleport(std::move(felix)); + BOOST_TEST(animal->name == "Felix"); + BOOST_TEST(felix.name == ""); + } +} +} // namespace TEST_NS + +namespace TEST_NS { + +// ----------------------------------------------------------------------------- +// pass virtual args by shared_ptr by value + +using policy = test_policy_<__COUNTER__>; +using namespace animals; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat, policy); + +BOOST_OPENMETHOD( + name, (virtual_>), std::string, policy); + +BOOST_OPENMETHOD_OVERRIDE(name, (std::shared_ptr cat), std::string) { + return cat->owner + "'s cat " + cat->name; +} + +BOOST_OPENMETHOD_OVERRIDE(name, (std::shared_ptr dog), std::string) { + return dog->owner + "'s dog " + dog->name; +} + +BOOST_AUTO_TEST_CASE(cast_args_shared_ptr_by_value) { + initialize(); + + auto spot = std::make_shared("Spot"); + BOOST_TEST(name(spot) == "Bill's dog Spot"); + + auto felix = std::make_shared("Felix"); + BOOST_TEST(name(felix) == "Bill's cat Felix"); +} +} // namespace TEST_NS + +namespace TEST_NS { + +using policy = test_policy_<__COUNTER__>; +using namespace animals; +} // namespace TEST_NS + +namespace TEST_NS { + +// ----------------------------------------------------------------------------- +// pass virtual args by shared_ptr by ref + +using policy = test_policy_<__COUNTER__>; +using namespace animals; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat, policy); + +BOOST_OPENMETHOD( + name, (virtual_&>), std::string, + policy); + +BOOST_OPENMETHOD_OVERRIDE( + name, (const std::shared_ptr& cat), std::string) { + return cat->owner + "'s cat " + cat->name; +} + +BOOST_OPENMETHOD_OVERRIDE( + name, (const std::shared_ptr& dog), std::string) { + return dog->owner + "'s dog " + dog->name; +} + +BOOST_AUTO_TEST_CASE(cast_args_shared_ptr_by_ref) { + initialize(); + + auto spot = std::make_shared("Spot"); + BOOST_TEST(name(spot) == "Bill's dog Spot"); + + auto felix = std::make_shared("Felix"); + BOOST_TEST(name(felix) == "Bill's cat Felix"); +} + +} // namespace TEST_NS + +namespace TEST_NS { + +// ----------------------------------------------------------------------------- +// pass virtual args by unique_ptr + +using policy = test_policy_<__COUNTER__>; +using namespace animals; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat, policy); + +BOOST_OPENMETHOD( + name, (virtual_>), std::string, policy); + +BOOST_OPENMETHOD_OVERRIDE(name, (std::unique_ptr cat), std::string) { + return cat->owner + "'s cat " + cat->name; +} + +BOOST_OPENMETHOD_OVERRIDE(name, (std::unique_ptr dog), std::string) { + return dog->owner + "'s dog " + dog->name; +} + +BOOST_AUTO_TEST_CASE(cast_args_unique_ptr) { + initialize(); + + auto spot = std::make_unique("Spot"); + BOOST_TEST(name(std::move(spot)) == "Bill's dog Spot"); + BOOST_TEST(spot.get() == nullptr); + + auto felix = std::make_unique("Felix"); + BOOST_TEST(name(std::move(felix)) == "Bill's cat Felix"); + BOOST_TEST(felix.get() == nullptr); +} +} // namespace TEST_NS + +namespace matrices { + +using policy = test_policy_<__COUNTER__>; + +#define TYPES \ + NONE, MATRIX, DIAGONAL, SCALAR_MATRIX, SCALAR_DIAGONAL, MATRIX_SCALAR, \ + DIAGONAL_SCALAR, MATRIX_MATRIX, MATRIX_DIAGONAL, DIAGONAL_DIAGONAL, \ + DIAGONAL_MATRIX, MATRIX_DENSE, DENSE_MATRIX + +enum Type { TYPES }; + +using Types = std::pair; + +std::ostream& operator<<(std::ostream& os, Type t) { +#define TYPE_TO_STRING(r, data, elem) BOOST_PP_STRINGIZE(elem), + static const char* names[] = {BOOST_PP_SEQ_FOR_EACH( + TYPE_TO_STRING, _, BOOST_PP_VARIADIC_TO_SEQ(TYPES))}; + return os << names[t]; +} + +std::ostream& operator<<(std::ostream& os, Types o) { + os << o.first << ", " << o.second; + return os; +} + +struct matrix { + virtual ~matrix() { + } + + Type type; +}; + +struct dense_matrix : matrix {}; +struct diagonal_matrix : matrix {}; + +} // namespace matrices + +namespace TEST_NS { + +using namespace matrices; + +BOOST_OPENMETHOD_CLASSES(matrix, dense_matrix, diagonal_matrix, policy); + +BOOST_OPENMETHOD( + times, (virtual_, virtual_), Types, policy); +BOOST_OPENMETHOD(times, (double, virtual_), Types, policy); +BOOST_OPENMETHOD(times, (virtual_, double), Types, policy); + +BOOST_OPENMETHOD_OVERRIDE(times, (const matrix&, const matrix&), Types) { + return Types(MATRIX_MATRIX, NONE); +} + +BOOST_OPENMETHOD_OVERRIDE( + times, (const diagonal_matrix&, const diagonal_matrix&), Types) { + return Types(DIAGONAL_DIAGONAL, MATRIX_MATRIX); +} + +BOOST_OPENMETHOD_OVERRIDE(times, (double a, const matrix& m), Types) { + return Types(SCALAR_MATRIX, NONE); +} + +BOOST_OPENMETHOD_OVERRIDE(times, (double a, const diagonal_matrix& m), Types) { + return Types(SCALAR_DIAGONAL, SCALAR_MATRIX); +} + +BOOST_OPENMETHOD_OVERRIDE(times, (const diagonal_matrix& m, double a), Types) { + return Types(DIAGONAL_SCALAR, MATRIX_SCALAR); +} + +BOOST_OPENMETHOD_OVERRIDE(times, (const matrix& m, double a), Types) { + return Types(MATRIX_SCALAR, NONE); +} + +BOOST_AUTO_TEST_CASE(simple) { + auto report = initialize(); + + { + // pass by const ref + const matrix& dense = dense_matrix(); + const matrix& diag = diagonal_matrix(); + BOOST_TEST(times(dense, dense) == Types(MATRIX_MATRIX, NONE)); + BOOST_TEST( + times(diag, diag) == Types(DIAGONAL_DIAGONAL, MATRIX_MATRIX)); + BOOST_TEST(times(diag, dense) == Types(MATRIX_MATRIX, NONE)); + BOOST_TEST(times(2, dense) == Types(SCALAR_MATRIX, NONE)); + BOOST_TEST(times(dense, 2) == Types(MATRIX_SCALAR, NONE)); + BOOST_TEST(times(diag, 2) == Types(DIAGONAL_SCALAR, MATRIX_SCALAR)); + } +} + +} // namespace TEST_NS + +namespace TEST_NS { + +using namespace matrices; + +using policy = test_policy_<__COUNTER__>; + +BOOST_OPENMETHOD_CLASSES(matrix, dense_matrix, diagonal_matrix, policy); + +BOOST_OPENMETHOD( + times, (virtual_, virtual_), Types, policy); + +BOOST_OPENMETHOD_OVERRIDE(times, (const matrix&, const matrix&), Types) { + BOOST_TEST(!has_next()); + return Types(MATRIX_MATRIX, NONE); +} + +BOOST_OPENMETHOD_OVERRIDE( + times, (const matrix& a, const diagonal_matrix& b), Types) { + BOOST_TEST(has_next()); + return Types(MATRIX_DIAGONAL, next(a, b).first); +} + +BOOST_OPENMETHOD_OVERRIDE( + times, (const diagonal_matrix& a, const matrix& b), Types) { + BOOST_TEST(has_next()); + return Types(DIAGONAL_MATRIX, next(a, b).first); +} + +BOOST_AUTO_TEST_CASE(ambiguity) { + auto compiler = initialize(); + BOOST_TEST(compiler.report.ambiguous == 1); + + // N2216: in case of ambiguity, pick one. + diagonal_matrix diag1, diag2; + auto result1 = times(diag1, diag2); + BOOST_TEST(result1.first == DIAGONAL_MATRIX); + // Which overrider is picked is NOT documented! However, I know that it is + // the last in registration order. This is important for the test for + // ambiguity resolution using covariant return types. + BOOST_TEST(result1.second == MATRIX_MATRIX); + + // but always the same + auto result2 = times(diag1, diag2); + BOOST_TEST((result1 == result2)); +} + +} // namespace TEST_NS + +namespace TEST_NS { + +using namespace matrices; + +using policy = test_policy_<__COUNTER__>; + +BOOST_OPENMETHOD_CLASSES(matrix, dense_matrix, policy); + +BOOST_OPENMETHOD( + times, (virtual_, virtual_), matrix*, policy); + +BOOST_OPENMETHOD_OVERRIDE( + times, (const matrix&, const dense_matrix&), matrix*) { + auto result = new dense_matrix; + result->type = MATRIX_DENSE; + return result; +} + +BOOST_OPENMETHOD_OVERRIDE( + times, (const dense_matrix&, const matrix&), dense_matrix*) { + auto result = new dense_matrix; + result->type = DENSE_MATRIX; + return result; +} + +BOOST_AUTO_TEST_CASE(covariant_return_type) { + auto compiler = initialize(); + BOOST_TEST(compiler.report.ambiguous == 0); + + // N2216: use covariant return types to resolve ambiguity. + dense_matrix left, right; + std::unique_ptr result(times(left, right)); + BOOST_TEST(result->type == DENSE_MATRIX); +} + +} // namespace TEST_NS + +namespace test_next_fn { + +struct Animal { + virtual ~Animal() { + } +}; + +struct Dog : Animal {}; +struct Bulldog : Dog {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog, Bulldog); + +struct BOOST_OPENMETHOD_NAME(poke); +using poke = + method), std::string>; + +std::string poke_dog(Dog& dog) { + return "bark"; +} + +BOOST_OPENMETHOD_REGISTER(poke::override); + +std::string poke_bulldog(Bulldog& dog) { + return poke::next(dog) + " and bite back"; +} + +BOOST_OPENMETHOD_REGISTER(poke::override); + +BOOST_AUTO_TEST_CASE(test_next_fn) { + initialize(); + + std::unique_ptr snoopy = std::make_unique(); + BOOST_TEST(poke::fn(*snoopy) == "bark"); + + std::unique_ptr hector = std::make_unique(); + BOOST_TEST(poke::fn(*hector) == "bark and bite back"); +} + +} // namespace test_next_fn + +namespace errors { + +using namespace matrices; + +BOOST_OPENMETHOD_CLASSES(matrix, dense_matrix, diagonal_matrix, matrix); + +BOOST_OPENMETHOD( + times, (virtual_, virtual_), void); + +BOOST_OPENMETHOD_OVERRIDE( + times, (const diagonal_matrix&, const matrix&), void) { +} + +BOOST_OPENMETHOD_OVERRIDE( + times, (const matrix&, const diagonal_matrix&), void) { +} + +void test_handler(const default_policy::error_variant& error_v) { + if (auto error = std::get_if(&error_v)) { + throw *error; + } + + if (auto error = std::get_if(&error_v)) { + throw *error; + } + + if (auto error = std::get_if(&error_v)) { + throw *error; + } + + throw int(); +} + +} // namespace errors + +namespace initialize_error_handling { + +using policy = test_policy_<__COUNTER__>; + +struct base { + virtual ~base() { + } +}; + +BOOST_OPENMETHOD(foo, (virtual_), void, policy); + +// instantiate the method +BOOST_OPENMETHOD_OVERRIDE(foo, (base&), void) { +} + +BOOST_AUTO_TEST_CASE(test_initialize_error_handling) { + auto prev_handler = policy::set_error_handler(errors::test_handler); + + try { + initialize(); + } catch (const unknown_class_error& error) { + policy::set_error_handler(prev_handler); + BOOST_TEST(error.type == reinterpret_cast(&typeid(base))); + return; + } catch (...) { + policy::set_error_handler(prev_handler); + BOOST_FAIL("unexpected exception"); + } + BOOST_FAIL("did not throw"); +} +} // namespace initialize_error_handling + +namespace across_namespaces { + +namespace animals { + +class Animal { + public: + virtual ~Animal() { + } +}; + +BOOST_OPENMETHOD(poke, (virtual_), std::string); + +} // namespace animals + +namespace more_animals { + +class Dog : public animals::Animal {}; + +BOOST_OPENMETHOD_CLASSES(Dog, animals::Animal); + +BOOST_OPENMETHOD_OVERRIDE(poke, (const Dog& dog), std::string) { + return "bark"; +} + +} // namespace more_animals + +BOOST_AUTO_TEST_CASE(across_namespaces) { + const animals::Animal& animal = more_animals::Dog(); + BOOST_TEST("bark" == poke(animal)); +} + +} // namespace across_namespaces + +namespace report { + +using policy = test_policy_<__COUNTER__>; + +struct Animal { + virtual void foo() = 0; +}; + +struct Dog : Animal { + void foo() override { + } +}; + +struct Cat : Animal { + void foo() override { + } +}; + +struct poke_; +struct pet_; +struct meet_; + +template +void fn(Class&...) { +} + +BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat, policy); + +BOOST_AUTO_TEST_CASE(initialize_report) { + using poke = method), void, policy>; + using pet = method), void, policy>; + using meet = + method, virtual_), void, policy>; + + auto report = initialize().report; + BOOST_TEST(report.not_implemented == 3); + BOOST_TEST(report.ambiguous == 0); + // 'meet' dispatch table is one cell, containing 'not_implemented' + BOOST_TEST(report.cells == 1); + + BOOST_OPENMETHOD_REGISTER(poke::override>); + report = initialize().report; + BOOST_TEST(report.not_implemented == 2); + + BOOST_OPENMETHOD_REGISTER(pet::override>); + BOOST_OPENMETHOD_REGISTER(pet::override>); + report = initialize().report; + BOOST_TEST(report.not_implemented == 2); + + // create ambiguity + BOOST_OPENMETHOD_REGISTER(meet::override>); + BOOST_OPENMETHOD_REGISTER(meet::override>); + report = initialize().report; + BOOST_TEST(report.cells == 4); + BOOST_TEST(report.ambiguous == 1); + + BOOST_OPENMETHOD_REGISTER(meet::override>); + report = initialize().report; + BOOST_TEST(report.cells == 6); + BOOST_TEST(report.ambiguous == 1); + + // shadow ambiguity + BOOST_OPENMETHOD_REGISTER(meet::override>); + BOOST_OPENMETHOD_REGISTER(meet::override>); + BOOST_OPENMETHOD_REGISTER(meet::override>); + report = initialize().report; + BOOST_TEST(report.cells == 9); + BOOST_TEST(report.ambiguous == 0); +} + +} // namespace report + +namespace test_comma_in_return_type { + +using policy = test_policy_<__COUNTER__>; + +struct Test { + virtual ~Test() {}; +}; + +BOOST_OPENMETHOD_CLASSES(Test, policy); + +BOOST_OPENMETHOD(foo, (virtual_), std::pair, policy); + +BOOST_OPENMETHOD_OVERRIDE(foo, (Test&), std::pair) { + return {1, 2}; +} + +BOOST_AUTO_TEST_CASE(comma_in_return_type) { + initialize(); + + Test test; + + BOOST_TEST((foo(test) == std::pair(1, 2))); +} + +} // namespace test_comma_in_return_type diff --git a/test/test_compiler.cpp b/test/test_compiler.cpp new file mode 100644 index 0000000..ebafb44 --- /dev/null +++ b/test/test_compiler.cpp @@ -0,0 +1,362 @@ +// Copyright (c) 2018-2025 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) + +#include +#include + +#include +#include +#include + +#include "test_util.hpp" + +#define BOOST_TEST_MODULE compiler +#include + +using namespace boost::openmethod; +using namespace boost::openmethod::detail; + +using class_ = generic_compiler::class_; +using cc_method = generic_compiler::method; +using overrider = generic_compiler::overrider; + +std::ostream& operator<<(std::ostream& os, const class_* cls) { + return os + << reinterpret_cast(cls->type_ids[0])->name(); +} + +std::string empty = "{}"; + +template class Container, typename T> +auto str(const Container& container) { + std::ostringstream os; + os << "{"; + const char* sep = ""; + + for (const auto& item : container) { + os << sep << item; + sep = ", "; + } + + os << "}"; + + return os.str(); +} + +template +auto sstr(Ts... args) { + std::vector vec{args...}; + std::sort(vec.begin(), vec.end()); + return str(vec); +} + +template +auto sstr(const std::unordered_set& container) { + return sstr(std::vector(container.begin(), container.end())); +} + +template +auto get_class(const Compiler& comp) { + return comp.class_map.at(typeid(T)); +} + +// A B +// \ / \ +// AB D +// | | +// C E +// \ / +// F + +struct A { + virtual ~A() { + } +}; + +struct B { + virtual ~B() { + } +}; + +struct AB : A, B {}; + +struct C : AB {}; + +struct D : B {}; + +struct E : D {}; + +struct F : C, E {}; + +// ============================================================================ +// Test use_classes. + +BOOST_AUTO_TEST_CASE(test_use_classes) { + using test_policy = test_policy_<__COUNTER__>; + BOOST_OPENMETHOD_REGISTER(use_classes); + + std::vector actual, expected; + + auto comp = initialize(); + + auto a = get_class(comp); + auto b = get_class(comp); + auto ab = get_class(comp); + auto c = get_class(comp); + auto d = get_class(comp); + auto e = get_class(comp); + + // ----------------------------------------------------------------------- + // A + BOOST_REQUIRE_EQUAL(sstr(a->direct_bases), empty); + BOOST_REQUIRE_EQUAL(sstr(a->direct_derived), sstr(ab)); + BOOST_REQUIRE_EQUAL(sstr(a->transitive_derived), sstr(a, ab, c)); + + // ----------------------------------------------------------------------- + // B + BOOST_REQUIRE_EQUAL(sstr(b->direct_bases), empty); + BOOST_REQUIRE_EQUAL(sstr(b->direct_derived), sstr(ab, d)); + BOOST_REQUIRE_EQUAL(sstr(b->transitive_derived), sstr(b, ab, c, d, e)); + + // ----------------------------------------------------------------------- + // AB + BOOST_REQUIRE_EQUAL(sstr(ab->direct_bases), sstr(a, b)); + BOOST_REQUIRE_EQUAL(sstr(ab->direct_derived), sstr(c)); + BOOST_REQUIRE_EQUAL(sstr(ab->transitive_derived), sstr(ab, c)); + + // ----------------------------------------------------------------------- + // C + BOOST_REQUIRE_EQUAL(sstr(c->direct_bases), sstr(ab)); + BOOST_REQUIRE_EQUAL(sstr(c->direct_derived), empty); + BOOST_REQUIRE_EQUAL(sstr(c->transitive_derived), sstr(c)); + + // ----------------------------------------------------------------------- + // D + BOOST_REQUIRE_EQUAL(sstr(d->direct_bases), sstr(b)); + BOOST_REQUIRE_EQUAL(sstr(d->direct_derived), sstr(e)); + BOOST_REQUIRE_EQUAL(sstr(d->transitive_derived), sstr(d, e)); + + // ----------------------------------------------------------------------- + // E + BOOST_REQUIRE_EQUAL(sstr(e->direct_bases), sstr(d)); + BOOST_REQUIRE_EQUAL(sstr(e->direct_derived), empty); + BOOST_REQUIRE_EQUAL(sstr(e->transitive_derived), sstr(e)); +} + +/// ============================================================================ +// Test assign_slots. + +template +const auto& get_method(const Compiler& comp, const method_info& info) { + for (const auto& m : comp.methods) { + if (m.info == &info) { + return m; + } + } + + BOOST_FAIL("method not found"); + + return comp.methods.front(); +} + +template +struct M; + +#define ADD_METHOD(CLASS) \ + auto& BOOST_PP_CAT(m_, CLASS) = \ + method), void, test_policy>::fn; + +#define ADD_METHOD_N(CLASS, N) \ + auto& BOOST_PP_CAT(BOOST_PP_CAT(m_, CLASS), N) = \ + method(virtual_), void, test_policy>::fn; + +BOOST_AUTO_TEST_CASE(test_assign_slots_a_b1_c) { + using test_policy = test_policy_<__COUNTER__>; + + // A + // / \ + // B1 C + + struct A {}; + struct B : A {}; + struct C : A {}; + + BOOST_OPENMETHOD_REGISTER(use_classes); + BOOST_OPENMETHOD_REGISTER(use_classes); + BOOST_OPENMETHOD_REGISTER(use_classes); + ADD_METHOD(B); + auto comp = initialize(); + + BOOST_TEST_REQUIRE(get_method(comp, m_B).slots.size() == 1); + BOOST_TEST(get_method(comp, m_B).slots[0] == 0); + BOOST_TEST(get_class(comp)->vtbl.size() == 0); + BOOST_TEST(get_class(comp)->vtbl.size() == 1); + BOOST_TEST(get_class(comp)->vtbl.size() == 0); +} + +BOOST_AUTO_TEST_CASE(test_assign_slots_a1_b1_c1) { + using test_policy = test_policy_<__COUNTER__>; + + // A1 + // / \ + // B1 C1 + + struct A {}; + struct B : A {}; + struct C : A {}; + + BOOST_OPENMETHOD_REGISTER(use_classes); + BOOST_OPENMETHOD_REGISTER(use_classes); + BOOST_OPENMETHOD_REGISTER(use_classes); + ADD_METHOD(A); + ADD_METHOD(B); + ADD_METHOD(C); + auto comp = initialize(); + + BOOST_TEST_REQUIRE(get_method(comp, m_A).slots.size() == 1); + BOOST_TEST(get_method(comp, m_A).slots[0] == 0); + BOOST_TEST(get_class(comp)->vtbl.size() == 1); + + BOOST_TEST_REQUIRE(get_method(comp, m_B).slots.size() == 1); + BOOST_TEST(get_method(comp, m_B).slots[0] == 1); + BOOST_TEST(get_class(comp)->vtbl.size() == 2); + + BOOST_TEST_REQUIRE(get_method(comp, m_C).slots.size() == 1); + BOOST_TEST(get_method(comp, m_C).slots[0] == 1); + BOOST_TEST(get_class(comp)->vtbl.size() == 2); +} + +BOOST_AUTO_TEST_CASE(test_assign_slots_a1_b1_d1_c1_d1) { + using test_policy = test_policy_<__COUNTER__>; + + // A1 + // / \ + // B1 C1 - slots 0-2 are wasted + // \ / + // D1 + + struct A {}; + struct B : virtual A {}; + struct C : virtual A {}; + struct D : B, C {}; + + BOOST_OPENMETHOD_REGISTER(use_classes); + BOOST_OPENMETHOD_REGISTER(use_classes); + BOOST_OPENMETHOD_REGISTER(use_classes); + BOOST_OPENMETHOD_REGISTER(use_classes); + ADD_METHOD(A); + ADD_METHOD(B); + ADD_METHOD(C); + ADD_METHOD(D); + auto comp = initialize(); + + BOOST_TEST_REQUIRE(get_method(comp, m_A).slots.size() == 1); + BOOST_TEST(get_method(comp, m_A).slots[0] == 0); + BOOST_TEST(get_class(comp)->vtbl.size() == 1); + + BOOST_TEST_REQUIRE(get_method(comp, m_B).slots.size() == 1); + BOOST_TEST(get_method(comp, m_B).slots[0] == 1); + BOOST_TEST(get_class(comp)->vtbl.size() == 2); + + BOOST_TEST_REQUIRE(get_method(comp, m_D).slots.size() == 1); + BOOST_TEST(get_method(comp, m_D).slots[0] == 2); + BOOST_TEST(get_class(comp)->vtbl.size() == 4); + + BOOST_TEST_REQUIRE(get_method(comp, m_C).slots.size() == 1); + BOOST_TEST(get_method(comp, m_C).slots[0] == 3); + BOOST_TEST(get_class(comp)->vtbl.size() == 4); + // slots 0-2 in C are wasted, to make room for methods in B and D +} + +BOOST_AUTO_TEST_CASE(test_assign_slots_a1_b1_d1_c1_d1_e2) { + using test_policy = test_policy_<__COUNTER__>; + + // A1 + // / \ + // B1 C1 slots 0-2 are wasted + // \ / \ + // D1 E2 but E can use them + + struct A {}; + struct B : virtual A {}; + struct C : virtual A {}; + struct E : C {}; + struct D : B, C {}; + + BOOST_OPENMETHOD_REGISTER(use_classes); + BOOST_OPENMETHOD_REGISTER(use_classes); + BOOST_OPENMETHOD_REGISTER(use_classes); + BOOST_OPENMETHOD_REGISTER(use_classes); + BOOST_OPENMETHOD_REGISTER(use_classes); + ADD_METHOD(A); + ADD_METHOD(B); + ADD_METHOD(C); + ADD_METHOD(D); + ADD_METHOD_N(E, 1); + ADD_METHOD_N(E, 2); + ADD_METHOD_N(E, 3); + auto comp = initialize(); + + BOOST_TEST_REQUIRE(get_method(comp, m_A).slots.size() == 1); + BOOST_TEST(get_method(comp, m_A).slots[0] == 0); + BOOST_TEST(get_class(comp)->vtbl.size() == 1); + + BOOST_TEST_REQUIRE(get_method(comp, m_B).slots.size() == 1); + BOOST_TEST(get_method(comp, m_B).slots[0] == 1); + BOOST_TEST(get_class(comp)->vtbl.size() == 2); + + BOOST_TEST_REQUIRE(get_method(comp, m_D).slots.size() == 1); + BOOST_TEST(get_method(comp, m_D).slots[0] == 2); + BOOST_TEST(get_class(comp)->vtbl.size() == 4); + + BOOST_TEST_REQUIRE(get_method(comp, m_C).slots.size() == 1); + BOOST_TEST(get_method(comp, m_C).slots[0] == 3); + BOOST_TEST(get_class(comp)->vtbl.size() == 4); + // slots 0-2 in C are wasted, to make room for methods in B and D + + BOOST_TEST_REQUIRE(get_method(comp, m_E1).slots.size() == 1); + BOOST_TEST(get_method(comp, m_E1).slots[0] == 1); + + BOOST_TEST_REQUIRE(get_method(comp, m_E2).slots.size() == 1); + BOOST_TEST(get_method(comp, m_E2).slots[0] == 2); + + BOOST_TEST_REQUIRE(get_method(comp, m_E3).slots.size() == 1); + BOOST_TEST(get_method(comp, m_E3).slots[0] == 4); + + BOOST_TEST(get_class(comp)->vtbl.size() == 5); +} + +BOOST_AUTO_TEST_CASE(test_assign_slots_a1_c1_b1) { + using test_policy = test_policy_<__COUNTER__>; + + // A1 B1 + // \ / + // C1 + + struct A {}; + struct B {}; + struct C : A, B {}; + + BOOST_OPENMETHOD_REGISTER(use_classes); + BOOST_OPENMETHOD_REGISTER(use_classes); + BOOST_OPENMETHOD_REGISTER(use_classes); + ADD_METHOD(A); + ADD_METHOD(B); + ADD_METHOD(C); + auto comp = initialize(); + + BOOST_TEST_REQUIRE(get_method(comp, m_A).slots.size() == 1); + BOOST_TEST(get_method(comp, m_A).slots[0] == 0); + BOOST_TEST(get_class(comp)->vtbl.size() == 1); + + BOOST_TEST_REQUIRE(get_method(comp, m_C).slots.size() == 1); + BOOST_TEST(get_method(comp, m_C).slots[0] == 1); + BOOST_TEST(get_class(comp)->vtbl.size() == 3); + + BOOST_TEST_REQUIRE(get_method(comp, m_B).slots.size() == 1); + BOOST_TEST(get_method(comp, m_B).slots[0] == 2); + BOOST_TEST(get_class(comp)->first_slot == 2); + BOOST_TEST(get_class(comp)->vtbl.size() == 1); +} diff --git a/test/test_core.cpp b/test/test_core.cpp new file mode 100644 index 0000000..01afaa6 --- /dev/null +++ b/test/test_core.cpp @@ -0,0 +1,339 @@ +// Copyright (c) 2018-2025 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) + +#include +#include + +#include + +#include "test_util.hpp" + +#define BOOST_TEST_MODULE core +#include + +using namespace boost::openmethod; +using namespace boost::openmethod::detail; +namespace mp11 = boost::mp11; + +// clang-format off + +namespace test_virtual { + +struct base { + virtual ~base() {} +}; + +struct a : base {}; +struct b : base {}; +struct c : base {}; +struct d : base {}; +struct e : base {}; +struct f : base {}; + +static_assert( + std::is_same_v< + virtual_traits::virtual_type, base>); + +static_assert( + std::is_same_v< + virtual_traits::virtual_type, base>); + +static_assert( + std::is_same_v< + virtual_traits::virtual_type, base>); + +static_assert( + std::is_same_v< + virtual_traits::virtual_type, void>); + +static_assert( + std::is_same_v< + boost::mp11::mp_filter< + is_virtual, + mp11::mp_list< virtual_, b, virtual_ > + >, + mp11::mp_list< virtual_, virtual_ > + >); + +static_assert( + std::is_same_v< + remove_virtual>, + a& + >); + +static_assert( + std::is_same_v< + virtual_type, + a + >); + +static_assert( + std::is_same_v< + boost::mp11::mp_transform< + remove_virtual, + mp11::mp_list< virtual_, virtual_ > + >, + mp11::mp_list + >); + +static_assert( + std::is_same_v< + boost::mp11::mp_transform_q< + boost::mp11::mp_bind_back, + boost::mp11::mp_transform< + remove_virtual, + mp11::mp_list< virtual_, virtual_ > + > + >, + mp11::mp_list + >); + +static_assert( + std::is_same_v< + boost::mp11::mp_transform_q< + boost::mp11::mp_bind_back, + boost::mp11::mp_transform< + remove_virtual, + boost::mp11::mp_filter< + is_virtual, + mp11::mp_list< virtual_, b, virtual_ > + > + > + >, + mp11::mp_list + >); + +// clang-format on + +static_assert(std::is_same_v< + virtual_types, b, virtual_>>, + mp11::mp_list>); + +static_assert(detail::is_policy); + +struct not_a_policy {}; +static_assert(!detail::is_policy); + +BOOST_AUTO_TEST_CASE(test_policy) { + { + // test is_policy_compatible + struct policy1 : default_policy::fork {}; + struct policy2 : default_policy::fork {}; + static_assert(detail::is_policy_compatible< + policy1, virtual_ptr>::value); + static_assert(detail::is_policy_compatible::value); + static_assert(!detail::is_policy_compatible< + policy1, virtual_ptr>::value); + } + + { + // check that forked policy does not share static data with original + struct policy : default_policy::fork {}; + BOOST_TEST(&policy::methods != &default_policy::methods); + BOOST_TEST(&policy::classes != &default_policy::classes); + BOOST_TEST( + &policy::static_vptr != &default_policy::static_vptr); + BOOST_TEST(&policy::dispatch_data != &default_policy::dispatch_data); + } + + { + // check that adding a facet keeps static data from original + struct policy : default_policy::add {}; + BOOST_TEST(&policy::methods == &default_policy::methods); + BOOST_TEST(&policy::classes == &default_policy::classes); + BOOST_TEST( + &policy::static_vptr == &default_policy::static_vptr); + BOOST_TEST(&policy::dispatch_data == &default_policy::dispatch_data); + } +} + +BOOST_AUTO_TEST_CASE(test_type_id_list) { + type_id expected[] = {type_id(&typeid(a)), type_id(&typeid(b))}; + auto iter = type_id_list, default_policy>::begin; + auto last = type_id_list, default_policy>::end; + BOOST_TEST_REQUIRE(last - iter == 2); + BOOST_TEST_REQUIRE(*iter++ == type_id(&typeid(a))); + BOOST_TEST_REQUIRE(*iter++ == type_id(&typeid(b))); +} + +} // namespace test_virtual + +namespace test_macros { + +// Check that macros can handle commas in parameter and return types. + +struct Animal { + virtual ~Animal() = default; +}; + +BOOST_OPENMETHOD(poke, (virtual_), std::tuple); + +} // namespace test_macros + +namespace casts { + +struct Animal { + virtual ~Animal() { + } + int a{1}; +}; + +struct Mammal : virtual Animal { + int m{2}; +}; + +struct Carnivore : virtual Animal { + int c{3}; +}; + +struct Dog : Mammal, Carnivore { + int d{4}; +}; + +const void* mammal_this(const Mammal& obj) { + return &obj; +} + +const void* carnivore_this(const Carnivore& obj) { + return &obj; +} + +const void* dog_this(const Dog& obj) { + return &obj; +} + +BOOST_AUTO_TEST_CASE(casts) { + Dog dog; + const Animal& animal = dog; + const Mammal& mammal = dog; + const Carnivore& carnivore = dog; + + BOOST_TEST( + (&virtual_traits::cast( + animal) + .m) == &dog.m); + BOOST_TEST( + (&virtual_traits::cast( + animal) + .c) == &dog.c); + BOOST_TEST( + (&virtual_traits::cast( + animal) + .m) == &dog.m); + BOOST_TEST( + (&virtual_traits::cast( + animal) + .d) == &dog.d); + BOOST_TEST( + (&virtual_traits::cast( + mammal) + .d) == &dog.d); + BOOST_TEST( + (&virtual_traits::cast( + carnivore) + .c) == &dog.c); + + using voidp = const void*; + using virtual_animal_t = virtual_type; + static_assert(std::is_same_v, "animal"); + using virtual_mammal_t = virtual_type; + static_assert(std::is_same_v, "mammal"); +} + +} // namespace casts + +namespace test_use_classes { + +struct Animal {}; +struct Dog : public Animal {}; +struct Bulldog : public Dog {}; +struct Cat : public Animal {}; +struct Dolphin : public Animal {}; + +static_assert( + std::is_same_v< + inheritance_map, + mp11::mp_list< + mp11::mp_list, mp11::mp_list, + mp11::mp_list, + mp11::mp_list, + mp11::mp_list>>); + +static_assert( + std::is_same_v< + use_classes::tuple_type, + std::tuple< + class_declaration_aux< + default_policy, mp11::mp_list>, + class_declaration_aux< + default_policy, mp11::mp_list>, + class_declaration_aux< + default_policy, mp11::mp_list>, + class_declaration_aux< + default_policy, mp11::mp_list>, + class_declaration_aux< + default_policy, mp11::mp_list>>>); + +} // namespace test_use_classes + +namespace facets { + +using namespace policies; + +struct key1; +struct key2; +struct alt_rtti {}; + +static_assert( + std::is_same_v>::type, domain>); + +struct policy1 : basic_policy {}; +struct policy2 : policy1::fork {}; +struct policy3 : policy1::fork::replace {}; + +static_assert(std::is_same_v>); + +static_assert(std::is_same_v>); + +} // namespace facets + +// ----------------------------------------------------------------------------- +// static_slots + +namespace test_static_slots { +struct Animal; +} + +namespace boost { +namespace openmethod { +namespace detail { + +template<> +struct static_offsets, + virtual_)>> { + static constexpr std::size_t slots[] = {0, 1}; +}; + +} // namespace detail +} // namespace openmethod +} // namespace boost + +namespace test_static_slots { + +struct Animal { + virtual ~Animal() { + } +}; + +using poke = method)>; +static_assert(!has_static_offsets::value); + +using meet = method, virtual_)>; +static_assert(has_static_offsets::value); + +} // namespace test_static_slots diff --git a/test/test_custom_rtti.cpp b/test/test_custom_rtti.cpp new file mode 100644 index 0000000..9208011 --- /dev/null +++ b/test/test_custom_rtti.cpp @@ -0,0 +1,484 @@ +// Copyright (c) 2018-2025 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) + +#define BOOST_TEST_MODULE openmethod +#include +#include + +#include +#include + +using namespace boost::openmethod; + +namespace type_hash { + +struct Animal { + const char* name; + + Animal(const char* name, const char* type) : name(name), type(type) { + } + + static constexpr const char* static_type = "Animal"; + const char* type; +}; + +struct Dog : Animal { + Dog(const char* name, const char* type = static_type) : Animal(name, type) { + } + + static constexpr const char* static_type = "Dog"; +}; + +struct Cat : Animal { + Cat(const char* name, const char* type = static_type) : Animal(name, type) { + } + + static constexpr const char* static_type = "Cat"; +}; + +struct custom_rtti : policies::rtti { + template + static auto static_type() { + if constexpr (std::is_base_of_v) { + return reinterpret_cast(T::static_type); + } else { + return 0; + } + } + + template + static auto dynamic_type(const T& obj) { + if constexpr (std::is_base_of_v) { + return reinterpret_cast(obj.type); + } else { + return 0; + } + } + + template + static void type_name(type_id type, Stream& stream) { + stream << (type == 0 ? "?" : reinterpret_cast(type)); + } + + static auto type_index(type_id type) { + return std::string_view( + (type == 0 ? "?" : reinterpret_cast(type))); + } +}; + +struct test_policy + : default_policy::fork::replace { +}; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat, test_policy); + +BOOST_OPENMETHOD(poke, (virtual_, std::ostream&), void, test_policy); + +BOOST_OPENMETHOD_OVERRIDE(poke, (Dog & dog, std::ostream& os), void) { + os << dog.name << " barks."; +} + +BOOST_OPENMETHOD_OVERRIDE(poke, (Cat & cat, std::ostream& os), void) { + os << cat.name << " hisses."; +} + +BOOST_AUTO_TEST_CASE(custom_rtti_simple_projection) { + initialize(); + + Animal &&a = Dog("Snoopy"), &&b = Cat("Sylvester"); + + { + std::stringstream os; + poke(a, os); + BOOST_TEST(os.str() == "Snoopy barks."); + } + { + std::stringstream os; + poke(b, os); + BOOST_TEST(os.str() == "Sylvester hisses."); + } +} +} // namespace type_hash + +namespace no_projection { + +struct Animal { + const char* name; + + Animal(const char* name, std::size_t type) : name(name), type(type) { + } + + static constexpr std::size_t static_type = 0; + std::size_t type; +}; + +struct Dog : Animal { + Dog(const char* name, std::size_t type = static_type) : Animal(name, type) { + } + + static constexpr std::size_t static_type = 1; +}; + +struct Cat : Animal { + Cat(const char* name, std::size_t type = static_type) : Animal(name, type) { + } + + static constexpr std::size_t static_type = 2; +}; + +struct custom_rtti : policies::rtti { + template + static auto static_type() { + if constexpr (std::is_base_of_v) { + return T::static_type; + } else { + return 666; + } + } + + template + static auto dynamic_type(const T& obj) { + if constexpr (std::is_base_of_v) { + return obj.type; + } else { + return 666; + } + } + + template + static void type_name(type_id type, Stream& stream) { + static const char* name[] = {"Animal", "Dog", "Cat"}; + stream << (type == 666 ? "?" : name[type]); + } + + static auto type_index(type_id type) { + return type; + } +}; + +struct test_policy + : default_policy::fork::replace< + policies::rtti, custom_rtti>::remove {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat, test_policy); + +BOOST_OPENMETHOD(poke, (virtual_, std::ostream&), void, test_policy); + +BOOST_OPENMETHOD_OVERRIDE(poke, (Dog & dog, std::ostream& os), void) { + os << dog.name << " barks."; +} + +BOOST_OPENMETHOD_OVERRIDE(poke, (Cat & cat, std::ostream& os), void) { + os << cat.name << " hisses."; +} + +void call_poke(Animal& a, std::ostream& os) { + return poke(a, os); +} +// mov rax, qword ptr [rdi + 8] +// mov rcx, qword ptr [rip + domain::context+24] +// mov rax, qword ptr [rcx + 8*rax] +// mov rcx, qword ptr [rip + method, basic_ostream >&)>::fn+80] mov +// rax, qword ptr [rax + 8*rcx] jmp rax # +// TAILCALL + +BOOST_AUTO_TEST_CASE(custom_rtti_simple) { + BOOST_TEST(Animal::static_type == 0); + BOOST_TEST(Dog::static_type == 1); + BOOST_TEST(Cat::static_type == 2); + initialize(); + + Animal &&a = Dog("Snoopy"), &&b = Cat("Sylvester"); + + { + std::stringstream os; + poke(a, os); + BOOST_TEST(os.str() == "Snoopy barks."); + } + { + std::stringstream os; + poke(b, os); + BOOST_TEST(os.str() == "Sylvester hisses."); + } +} + +namespace using_vptr { + +template +using vptr = virtual_ptr; + +BOOST_OPENMETHOD(poke, (vptr, std::ostream&), void, test_policy); + +BOOST_OPENMETHOD_OVERRIDE(poke, (vptr dog, std::ostream& os), void) { + os << dog->name << " barks."; +} + +BOOST_OPENMETHOD_OVERRIDE(poke, (vptr cat, std::ostream& os), void) { + os << cat->name << " hisses."; +} + +void call_poke(vptr a, std::ostream& os) { + // mov rax, qword ptr [rip + method::fn+80] + // mov rax, qword ptr [rsi + 8*rax] + // jmp rax # TAILCALL + return poke(a, os); +} + +} // namespace using_vptr +} // namespace no_projection + +namespace virtual_base { + +struct Animal { + const char* name; + + Animal(const char* name, std::size_t type) : name(name), type(type) { + } + + virtual void* cast_aux(std::size_t type) { + return type == static_type ? this : nullptr; + } + + static constexpr std::size_t static_type = 0; + std::size_t type; +}; + +template +Derived custom_dynamic_cast(Base& obj) { + using derived_type = std::remove_cv_t>; + return *reinterpret_cast( + const_cast&>(obj).cast_aux( + derived_type::static_type)); +} + +struct Dog : virtual Animal { + Dog(const char* name, std::size_t type = static_type) : Animal(name, type) { + } + + void* cast_aux(std::size_t type) override { + return type == static_type ? this : Animal::cast_aux(type); + } + + static constexpr std::size_t static_type = 1; +}; + +struct Cat : virtual Animal { + Cat(const char* name, std::size_t type = static_type) : Animal(name, type) { + } + + void* cast_aux(std::size_t type) override { + return type == static_type ? this : Animal::cast_aux(type); + } + + static constexpr std::size_t static_type = 2; +}; + +struct custom_rtti : policies::rtti { + template + static auto static_type() { + if constexpr (std::is_base_of_v) { + return T::static_type; + } else { + return 666; + } + } + + template + static auto dynamic_type(const T& obj) { + if constexpr (std::is_base_of_v) { + return obj.type; + } else { + return 666; + } + } + + template + static void type_name(type_id type, Stream& stream) { + static const char* name[] = {"Animal", "Dog", "Cat"}; + stream << (type == 666 ? "?" : name[type]); + } + + static auto type_index(type_id type) { + return type; + } + + template + static Derived dynamic_cast_ref(Base&& obj) { + return custom_dynamic_cast(obj); + } +}; + +struct test_policy + : default_policy::fork::replace< + policies::rtti, custom_rtti>::remove {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat, test_policy); + +BOOST_OPENMETHOD(poke, (virtual_, std::ostream&), void, test_policy); + +BOOST_OPENMETHOD_OVERRIDE(poke, (Dog & dog, std::ostream& os), void) { + os << dog.name << " barks."; +} + +BOOST_OPENMETHOD_OVERRIDE(poke, (Cat & cat, std::ostream& os), void) { + os << cat.name << " hisses."; +} + +void call_poke(Animal& a, std::ostream& os) { + return poke(a, os); +} +// mov rax, qword ptr [rdi + 8] +// mov rcx, qword ptr [rip + domain::context+24] +// mov rax, qword ptr [rcx + 8*rax] +// mov rcx, qword ptr [rip + method, basic_ostream >&)>::fn+80] mov +// rax, qword ptr [rax + 8*rcx] jmp rax # +// TAILCALL + +BOOST_AUTO_TEST_CASE(virtual_base) { + BOOST_TEST(Animal::static_type == 0); + BOOST_TEST(Dog::static_type == 1); + BOOST_TEST(Cat::static_type == 2); + initialize(); + + Animal &&a = Dog("Snoopy"), &&b = Cat("Sylvester"); + + { + std::stringstream os; + poke(a, os); + BOOST_TEST(os.str() == "Snoopy barks."); + } + { + std::stringstream os; + poke(b, os); + BOOST_TEST(os.str() == "Sylvester hisses."); + } +} + +namespace using_vptr { + +template +using vptr = virtual_ptr; + +BOOST_OPENMETHOD(poke, (vptr, std::ostream&), void, test_policy); + +BOOST_OPENMETHOD_OVERRIDE(poke, (vptr dog, std::ostream& os), void) { + os << dog->name << " barks."; +} + +BOOST_OPENMETHOD_OVERRIDE(poke, (vptr cat, std::ostream& os), void) { + os << cat->name << " hisses."; +} + +void call_poke(vptr a, std::ostream& os) { + // mov rax, qword ptr [rip + method::fn+80] + // mov rax, qword ptr [rsi + 8*rax] + // jmp rax # TAILCALL + return poke(a, os); +} + +} // namespace using_vptr +} // namespace virtual_base + +namespace defered_type_id { + +struct Animal { + const char* name; + + Animal(const char* name, std::size_t type) : name(name), type(type) { + } + + static std::size_t last_type_id; + static std::size_t static_type; + std::size_t type; +}; + +std::size_t Animal::last_type_id; +std::size_t Animal::static_type = ++Animal::last_type_id; + +struct Dog : Animal { + Dog(const char* name, std::size_t type = static_type) : Animal(name, type) { + } + + static std::size_t static_type; +}; + +std::size_t Dog::static_type = ++Animal::last_type_id; + +struct Cat : Animal { + Cat(const char* name, std::size_t type = static_type) : Animal(name, type) { + } + + static std::size_t static_type; +}; + +std::size_t Cat::static_type = ++Animal::last_type_id; + +struct custom_rtti : policies::deferred_static_rtti { + template + static auto static_type() { + if constexpr (std::is_base_of_v) { + return T::static_type; + } else { + static type_id invalid = 0; + return invalid; + } + } + + template + static auto dynamic_type(const T& obj) { + if constexpr (std::is_base_of_v) { + return obj.type; + } else { + return 666; + } + } + + template + static void type_name(type_id type, Stream& stream) { + static const char* name[] = {"Animal", "Dog", "Cat"}; + stream << (type == 0 ? "?" : name[type]); + } + + static auto type_index(type_id type) { + return type; + } +}; + +struct test_policy + : default_policy::fork::replace< + policies::rtti, custom_rtti>::remove {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat, test_policy); + +BOOST_OPENMETHOD(poke, (virtual_, std::ostream&), void, test_policy); + +BOOST_OPENMETHOD_OVERRIDE(poke, (Dog & dog, std::ostream& os), void) { + os << dog.name << " barks."; +} + +BOOST_OPENMETHOD_OVERRIDE(poke, (Cat & cat, std::ostream& os), void) { + os << cat.name << " hisses."; +} + +BOOST_AUTO_TEST_CASE(custom_rtti_deferred) { + initialize(); + + Animal &&a = Dog("Snoopy"), &&b = Cat("Sylvester"); + + { + std::stringstream os; + poke(a, os); + BOOST_TEST(os.str() == "Snoopy barks."); + } + { + std::stringstream os; + poke(b, os); + BOOST_TEST(os.str() == "Sylvester hisses."); + } +} + +} // namespace defered_type_id diff --git a/test/test_intrusive.cpp b/test/test_intrusive.cpp new file mode 100644 index 0000000..48197bc --- /dev/null +++ b/test/test_intrusive.cpp @@ -0,0 +1,186 @@ +// Copyright (c) 2018-2025 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) + +#include +#include + +#include + +namespace bom = boost::openmethod; +struct test_policy : bom::default_policy::remove {}; +#define BOOST_OPENMETHOD_DEFAULT_POLICY test_policy + +#include +#include + +#define BOOST_TEST_MODULE instrusive +#include +#include + +#include "test_util.hpp" + +namespace bom = boost::openmethod; +using bom::virtual_; + +struct Animal : bom::with_vptr { + Animal(); + ~Animal(); +}; + +struct Cat : Animal, bom::with_vptr { + Cat(); + ~Cat(); +}; + +struct Pet : bom::with_vptr { + Pet(); + ~Pet(); + std::string name; +}; + +struct DomesticCat : Cat, Pet, bom::with_vptr { + DomesticCat(); + ~DomesticCat(); +}; + +BOOST_OPENMETHOD( + speak, (virtual_ animal, std::ostream& os), void); +BOOST_OPENMETHOD(describe, (virtual_ pet, std::ostream& os), void); + +Animal::Animal() { + speak(*this, std::cout); +} + +Animal::~Animal() { + speak(*this, std::cout); +} + +Cat::Cat() { + speak(*this, std::cout); +} + +Cat::~Cat() { + speak(*this, std::cout); +} + +Pet::Pet() { + describe(*this, std::cout); +} + +Pet::~Pet() { + describe(*this, std::cout); +} + +DomesticCat::DomesticCat() { + name = "Felix"; + describe(*this, std::cout); +} + +DomesticCat::~DomesticCat() { + name = "Felix"; + describe(*this, std::cout); +} + +BOOST_OPENMETHOD_OVERRIDE( + speak, (const Animal& animal, std::ostream& os), void) { + os << "???\n"; +} + +BOOST_OPENMETHOD_OVERRIDE(speak, (const Cat& animal, std::ostream& os), void) { + os << "meow\n"; +} + +BOOST_OPENMETHOD_OVERRIDE(describe, (const Pet& pet, std::ostream& os), void) { + os << "I am a pet\n"; +} + +BOOST_OPENMETHOD_OVERRIDE( + describe, (const DomesticCat& pet, std::ostream& os), void) { + os << "I am " << pet.name << " the cat\n"; +} + +// Check that we pick one of the vptrs in presence of MI, dodging ambiguity +// issues. +BOOST_OPENMETHOD( + cat_influencer, (virtual_ cat, std::ostream& os), void); + +BOOST_OPENMETHOD_OVERRIDE( + cat_influencer, (const DomesticCat& cat, std::ostream& os), void) { + os << "Follow " << cat.name << " the cat on YouTube\n"; +} + +BOOST_AUTO_TEST_CASE(intrusive_mode) { + bom::initialize(); + + std::unique_ptr cat; + + { + boost::test_tools::output_test_stream output; + { + capture_cout capture(output.rdbuf()); + cat = std::make_unique(); + } + + BOOST_CHECK(output.is_equal( + "???\n" + "meow\n" + "I am a pet\n" + "I am Felix the cat\n")); + } + + { + boost::test_tools::output_test_stream output; + + { + capture_cout capture(output.rdbuf()); + describe(*cat, std::cout); + } + + BOOST_CHECK(output.is_equal("I am Felix the cat\n")); + } + + { + boost::test_tools::output_test_stream output; + + { + capture_cout capture(output.rdbuf()); + cat_influencer(*cat, std::cout); + } + + BOOST_CHECK(output.is_equal("Follow Felix the cat on YouTube\n")); + } + + { + boost::test_tools::output_test_stream output; + { + capture_cout capture(output.rdbuf()); + cat.reset(); + } + + BOOST_CHECK(output.is_equal( + "I am Felix the cat\n" + "I am a pet\n" + "meow\n" + "???\n")); + } +} + +struct indirect_policy : test_policy::add {}; + +struct Indirect : bom::with_vptr { + using bom::with_vptr::boost_openmethod_vptr; +}; + +BOOST_OPENMETHOD(whatever, (virtual_), void, indirect_policy); + +BOOST_OPENMETHOD_OVERRIDE(whatever, (Indirect&), void) { +} + +BOOST_AUTO_TEST_CASE(core_intrusive_vptr) { + bom::initialize(); + Indirect i; + BOOST_TEST( + i.boost_openmethod_vptr == &indirect_policy::static_vptr); +} diff --git a/test/test_member_method.cpp b/test/test_member_method.cpp new file mode 100644 index 0000000..131b9dd --- /dev/null +++ b/test/test_member_method.cpp @@ -0,0 +1,68 @@ +// Copyright (c) 2018-2025 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) + +#include +#include + +#include +#include +#include + +using namespace boost::openmethod; +using std::cout; + +struct Role { + virtual ~Role() { + } +}; + +struct Employee : Role {}; + +struct Manager : Employee {}; + +BOOST_OPENMETHOD_CLASSES(Role, Employee, Manager); + +struct Payroll { + int balance{10000}; + + void pay(const Role& role) { + pay_method::fn(this, role); + } + + private: + struct BOOST_OPENMETHOD_NAME(pay); + using pay_method = method< + BOOST_OPENMETHOD_NAME(pay)(Payroll*, virtual_), void>; + + void pay_employee(const Employee&) { + balance -= 2000; + } + void pay_manager(const Manager& manager) { + pay_method::next<&Payroll::pay_manager>(this, manager); + balance -= 1000; + } + + public: + using pay_functions = Payroll::pay_method::override< + &Payroll::pay_employee, &Payroll::pay_manager>; +}; + +BOOST_OPENMETHOD_REGISTER(Payroll::pay_functions); + +#define BOOST_TEST_MODULE openmethod +#include + +BOOST_AUTO_TEST_CASE(member_method) { + initialize(); + + Payroll pr; + const Employee& alice = Employee(); + const Manager& bob = Manager(); + + pr.pay(alice); + BOOST_TEST(pr.balance == 8000); + pr.pay(bob); + BOOST_TEST(pr.balance == 5000); +} diff --git a/test/test_namespaces.cpp b/test/test_namespaces.cpp new file mode 100644 index 0000000..db61648 --- /dev/null +++ b/test/test_namespaces.cpp @@ -0,0 +1,107 @@ +// Copyright (c) 2018-2025 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) + +namespace interfaces { +class Animal { + public: + virtual ~Animal() { + } +}; +} // namespace interfaces + +namespace canis { +class Dog : public interfaces::Animal {}; +class Bulldog : public Dog {}; +} // namespace canis + +namespace felis { +class Cat : public interfaces::Animal {}; +} // namespace felis + +namespace delphinus { +class Dolphin : public interfaces::Animal {}; +} // namespace delphinus + +#include + +#include +#include + +using boost::openmethod::virtual_; + +BOOST_OPENMETHOD_CLASSES( + interfaces::Animal, canis::Dog, canis::Bulldog, felis::Cat, + delphinus::Dolphin); + +// open method with single virtual argument <=> virtual function "from outside" +BOOST_OPENMETHOD(poke, (virtual_), std::string); + +namespace canis { +// implement 'poke' for dogs +BOOST_OPENMETHOD_OVERRIDE(poke, (Dog & dog), std::string) { + return "bark"; +} + +// implement 'poke' for bulldogs +BOOST_OPENMETHOD_OVERRIDE(poke, (Bulldog & dog), std::string) { + return next(dog) + " and bite"; +} +} // namespace canis + +// multi-method +BOOST_OPENMETHOD( + meet, (virtual_, virtual_), + std::string); + +// 'meet' catch-all implementation +BOOST_OPENMETHOD_OVERRIDE( + meet, (interfaces::Animal&, interfaces::Animal&), std::string) { + return "ignore"; +} + +BOOST_OPENMETHOD_OVERRIDE( + meet, (canis::Dog & dog1, canis::Dog& dog2), std::string) { + return "wag tail"; +} + +BOOST_OPENMETHOD_OVERRIDE( + meet, (canis::Dog & dog, felis::Cat& cat), std::string) { + return "chase"; +} + +BOOST_OPENMETHOD_OVERRIDE( + meet, (felis::Cat & cat, canis::Dog& dog), std::string) { + return "run"; +} + +// ----------------------------------------------------------------------------- +// main + +#include +#include + +int main() { + boost::openmethod::initialize(); + + std::unique_ptr hector = + std::make_unique(), + snoopy = std::make_unique(); + + std::cout << "poke snoopy: " << poke(*snoopy) << "\n"; // bark + std::cout << "poke hector: " << poke(*hector) << "\n"; // bark and bite + + std::unique_ptr + sylvester = std::make_unique(), + flipper = std::make_unique(); + + std::cout << "hector meets sylvester: " << meet(*hector, *sylvester) + << "\n"; // chase + std::cout << "sylvester meets hector: " << meet(*sylvester, *hector) + << "\n"; // run + std::cout << "hector meets snoopy: " << meet(*hector, *snoopy) + << "\n"; // wag tail + std::cout << "hector meets flipper: " << meet(*hector, *flipper) + << "\n"; // ignore +} diff --git a/test/test_pointer_to_method.cpp b/test/test_pointer_to_method.cpp new file mode 100644 index 0000000..90f3824 --- /dev/null +++ b/test/test_pointer_to_method.cpp @@ -0,0 +1,38 @@ +// Copyright (c) 2018-2025 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) + +#include +#include + +#include + +#define BOOST_TEST_MODULE openmethod +#include + +using boost::openmethod::virtual_; + +class Animal { + public: + virtual ~Animal() { + } +}; + +class Dog : public Animal {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog, Animal); + +BOOST_OPENMETHOD(poke, (virtual_), std::string); + +BOOST_OPENMETHOD_OVERRIDE(poke, (Dog & dog), std::string) { + return "bark"; +} + +BOOST_AUTO_TEST_CASE(noadl) { + boost::openmethod::initialize(); + std::string (*stimulus)(Animal&) = poke; + Dog snoopy; + Animal& animal = snoopy; + BOOST_TEST(stimulus(snoopy) == "bark"); +} diff --git a/test/test_rolex.cpp b/test/test_rolex.cpp new file mode 100644 index 0000000..e3c976f --- /dev/null +++ b/test/test_rolex.cpp @@ -0,0 +1,102 @@ +// Copyright (c) 2018-2025 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) + +#include +#include + +struct Role { + virtual ~Role() { + } +}; + +struct Employee : Role { + virtual double pay(); +}; + +struct Manager : Employee { + virtual double pay(); +}; + +struct Founder : Role {}; + +struct Expense { + virtual ~Expense() { + } +}; + +struct Public : Expense {}; +struct Bus : Public {}; +struct Metro : Public {}; +struct Taxi : Expense {}; +struct Jet : Expense {}; + +BOOST_OPENMETHOD_CLASSES( + Role, Employee, Manager, Founder, Expense, Public, Bus, Metro, Taxi, Jet); + +//static_assert(!virtual_ptr::IsSmartPtr); +BOOST_OPENMETHOD(pay, (virtual_ptr), double); +BOOST_OPENMETHOD( + approve, (virtual_ptr, virtual_ptr, double), + bool); + +BOOST_OPENMETHOD_OVERRIDE(pay, (virtual_ptr), double) { + return 3000; +} + +BOOST_OPENMETHOD_OVERRIDE(pay, (virtual_ptr exec), double) { + return next(exec) + 2000; +} + +BOOST_OPENMETHOD_OVERRIDE( + approve, + (virtual_ptr r, virtual_ptr e, double amount), + bool) { + return false; +} + +BOOST_OPENMETHOD_OVERRIDE( + approve, + (virtual_ptr r, virtual_ptr e, double amount), + bool) { + return true; +} + +BOOST_OPENMETHOD_OVERRIDE( + approve, + (virtual_ptr r, virtual_ptr e, double amount), + bool) { + return true; +} + +BOOST_OPENMETHOD_OVERRIDE( + approve, + (virtual_ptr r, virtual_ptr e, double amount), + bool) { + return true; +} + +int main() { + boost::openmethod::initialize(); +} + +double call_pay(Employee& emp) { + return pay(emp); +} + +double Employee::pay() { + return 3000; +} + +double Manager::pay() { + return Employee::pay() + 2000; +} + +double call_pay_vfunc(Employee& emp) { + return emp.pay(); +} + +bool call_approve(const Role& r, const Expense& e, double a) { + return approve(r, e, a); +} diff --git a/test/test_static_list.cpp b/test/test_static_list.cpp new file mode 100644 index 0000000..e4a474b --- /dev/null +++ b/test/test_static_list.cpp @@ -0,0 +1,160 @@ +// Copyright (c) 2018-2025 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) + +#include "boost/openmethod/detail/static_list.hpp" + +#define BOOST_TEST_MODULE openmethod +#include + +using boost::openmethod::detail::static_list; + +struct value : static_list::static_link {}; + +using test_iter = static_list::iterator; +BOOST_TEST_DONT_PRINT_LOG_VALUE(test_iter); + +BOOST_AUTO_TEST_CASE(test_list) { + static static_list l; + static value a, b, c, d; + + BOOST_TEST_REQUIRE(l.begin() == l.end()); + BOOST_TEST_REQUIRE(l.empty()); + + l.push_back(a); + // a + BOOST_TEST_REQUIRE(&*l.begin() == &a); + BOOST_TEST_REQUIRE(!l.empty()); + BOOST_TEST_REQUIRE(std::distance(l.begin(), l.end()) == 1); + + static_list::iterator iter; + + l.push_back(b); + // a b + BOOST_TEST_REQUIRE(std::distance(l.begin(), l.end()) == 2); + + // test iterator post-increment + iter = l.begin(); + BOOST_TEST_REQUIRE(iter != l.end()); + BOOST_TEST(&*iter++ == &a); + BOOST_TEST_REQUIRE(iter != l.end()); + BOOST_TEST(&*iter++ == &b); + BOOST_TEST(iter == l.end()); + + // test iterator pre-increment + iter = l.begin(); + ++iter; + BOOST_TEST_REQUIRE(&*iter == &b); + + l.push_back(c); + // a b c + BOOST_TEST_REQUIRE(std::distance(l.begin(), l.end()) == 3); + + // test remove from back, the most common case + l.remove(c); + // a b + + BOOST_TEST_REQUIRE(std::distance(l.begin(), l.end()) == 2); + iter = l.begin(); + BOOST_TEST_REQUIRE(&*iter++ == &a); + BOOST_TEST_REQUIRE(&*iter++ == &b); + + l.push_back(c); + // a b c + BOOST_TEST_REQUIRE(std::distance(l.begin(), l.end()) == 3); + + l.push_back(d); + // a b c d + BOOST_TEST_REQUIRE(std::distance(l.begin(), l.end()) == 4); + + // test remove from front + l.remove(a); + // b c d + + BOOST_TEST_REQUIRE(std::distance(l.begin(), l.end()) == 3); + iter = l.begin(); + BOOST_TEST_REQUIRE(&*iter++ == &b); + BOOST_TEST_REQUIRE(&*iter++ == &c); + BOOST_TEST_REQUIRE(&*iter++ == &d); + + // test remove from middle + l.remove(b); + // c d + + BOOST_TEST_REQUIRE(std::distance(l.begin(), l.end()) == 2); + iter = l.begin(); + BOOST_TEST_REQUIRE(&*iter++ == &c); + BOOST_TEST_REQUIRE(&*iter++ == &d); + + // test remove from end + l.remove(c); + // d + + iter = l.begin(); + BOOST_TEST_REQUIRE(std::distance(l.begin(), l.end()) == 1); + BOOST_TEST_REQUIRE(&*iter++ == &d); + + // test remove from end, one item left + l.remove(d); + BOOST_TEST_REQUIRE(std::distance(l.begin(), l.end()) == 0); +} + +struct static_value : static_list::static_link { + explicit static_value(static_list& reg) { + reg.push_back(*this); + } +}; + +// Check that static links and list work regardless of static initialization +// order. + +using static_test_iter = static_list::iterator; +BOOST_TEST_DONT_PRINT_LOG_VALUE(static_test_iter); + +namespace link_link_list { +extern static_list reg; +static_value a(reg); +static_value b(reg); +static_list reg; + +BOOST_AUTO_TEST_CASE(test_static_link_link_list) { + auto iter = reg.begin(), last = reg.end(); + BOOST_TEST_REQUIRE(iter != last); + BOOST_TEST_REQUIRE(&*iter == &a); + BOOST_TEST_REQUIRE(++iter != last); + BOOST_TEST_REQUIRE(&*iter == &b); + BOOST_TEST_REQUIRE(++iter == last); +} +} // namespace link_link_list + +namespace link_list_link { +extern static_list reg; +static_value a(reg); +static_list reg; +static_value b(reg); + +BOOST_AUTO_TEST_CASE(test_static_link_list_link) { + auto iter = reg.begin(), last = reg.end(); + BOOST_TEST_REQUIRE(iter != last); + BOOST_TEST_REQUIRE(&*iter == &a); + BOOST_TEST_REQUIRE(++iter != last); + BOOST_TEST_REQUIRE(&*iter == &b); + BOOST_TEST_REQUIRE(++iter == last); +} +} // namespace link_list_link + +namespace list_link_link { +static_list reg; +static_value a(reg); +static_value b(reg); + +BOOST_AUTO_TEST_CASE(test_static_list_link_link) { + auto iter = reg.begin(), last = reg.end(); + BOOST_TEST_REQUIRE(iter != last); + BOOST_TEST_REQUIRE(&*iter == &a); + BOOST_TEST_REQUIRE(++iter != last); + BOOST_TEST_REQUIRE(&*iter == &b); + BOOST_TEST_REQUIRE(++iter == last); +} +} // namespace list_link_link diff --git a/test/test_util.hpp b/test/test_util.hpp new file mode 100644 index 0000000..e07fe62 --- /dev/null +++ b/test/test_util.hpp @@ -0,0 +1,39 @@ +// Copyright (c) 2018-2025 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) + +#ifndef BOOST_OPENMETHOD_TEST_HELPERS_HPP +#define BOOST_OPENMETHOD_TEST_HELPERS_HPP + +#include + +#include +#include + +template +struct test_policy_ : +#ifdef NDEBUG + boost::openmethod::policies::release::fork> +#else + boost::openmethod::policies::debug::fork> +#endif +{ +}; + +#define TEST_NS BOOST_PP_CAT(test, __COUNTER__) + +struct capture_cout { + capture_cout(std::streambuf* new_buffer) + : old(std::cout.rdbuf(new_buffer)) { + } + + ~capture_cout() { + std::cout.rdbuf(old); + } + + private: + std::streambuf* old; +}; + +#endif diff --git a/test/test_virtual_ptr_all.cpp b/test/test_virtual_ptr_all.cpp new file mode 100644 index 0000000..6fd26d4 --- /dev/null +++ b/test/test_virtual_ptr_all.cpp @@ -0,0 +1,195 @@ +// Copyright (c) 2018-2025 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) + +#include +#include +#include + +#include "test_util.hpp" + +#define BOOST_TEST_MODULE openmethod +#include +#include + +using namespace boost::openmethod; + +struct Player { + virtual ~Player() { + } +}; + +struct Warrior : Player {}; +struct Wizard : Player {}; + +struct Bear : Player {}; + +struct Object { + virtual ~Object() { + } +}; + +struct Axe : Object {}; + +template +auto poke_bear(VirtualBearPtr) { + return std::string("growl"); +} + +template +auto fight_bear(VirtualWarriorPtr, VirtualAxePtr, VirtualBearPtr) { + return "kill bear"; +} + +template +struct indirect_test_policy : test_policy_ {}; + +template +using policy_types = + boost::mp11::mp_list, indirect_test_policy>; + +struct BOOST_OPENMETHOD_NAME(poke); +struct BOOST_OPENMETHOD_NAME(fight); + +namespace BOOST_OPENMETHOD_GENSYM { + +BOOST_AUTO_TEST_CASE_TEMPLATE( + test_virtual_ptr, Policy, policy_types<__COUNTER__>) { + + BOOST_OPENMETHOD_REGISTER( + use_classes); + ; + using poke = method< + BOOST_OPENMETHOD_NAME(poke)(virtual_ptr), std::string, + Policy>; + BOOST_OPENMETHOD_REGISTER( + typename poke::template override< + poke_bear>>); + + initialize(); + + using vptr_player = virtual_ptr; + static_assert(detail::is_virtual_ptr); + using vptr_bear = virtual_ptr; + + Player player; + auto virtual_player = vptr_player::final(player); + BOOST_TEST(&*virtual_player == &player); + BOOST_TEST((virtual_player.vptr() == Policy::template static_vptr)); + + Bear bear; + BOOST_TEST((&*vptr_bear::final(bear)) == &bear); + BOOST_TEST( + (vptr_bear::final(bear).vptr() == Policy::template static_vptr)); + + BOOST_TEST( + (vptr_player(bear).vptr() == Policy::template static_vptr)); + + vptr_bear virtual_bear_ptr(bear); + vptr_player virtual_player_ptr = virtual_bear_ptr; + + struct upcast { + static void fn(vptr_player) { + } + }; + + upcast::fn(virtual_bear_ptr); + + auto data = Policy::dispatch_data.data(); + std::fill_n(data, Policy::dispatch_data.size(), 0); + + while (data == Policy::dispatch_data.data()) { + Policy::dispatch_data.resize(2 * Policy::dispatch_data.size()); + } + + initialize(); + + BOOST_TEST( + (virtual_bear_ptr.vptr() == Policy::template static_vptr) == + Policy::template has_facet); +} +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace test_virtual_ptr_dispatch { + +BOOST_AUTO_TEST_CASE_TEMPLATE( + test_virtual_ptr_dispatch, Policy, policy_types<__COUNTER__>) { + + BOOST_OPENMETHOD_REGISTER( + use_classes); + + using poke = method< + BOOST_OPENMETHOD_NAME(poke)(virtual_ptr), std::string, + Policy>; + BOOST_OPENMETHOD_REGISTER( + typename poke::template override< + poke_bear>>); + + using fight = method< + BOOST_OPENMETHOD_NAME(fight)( + virtual_ptr, virtual_ptr, + virtual_ptr), + std::string, Policy>; + BOOST_OPENMETHOD_REGISTER( + typename fight::template override, virtual_ptr, + virtual_ptr>>); + + initialize(); + + Bear bear; + BOOST_TEST(poke::fn(virtual_ptr(bear)) == "growl"); + + Warrior warrior; + Axe axe; + BOOST_TEST( + fight::fn( + virtual_ptr(warrior), + virtual_ptr(axe), + virtual_ptr(bear)) == "kill bear"); +} + +} // namespace test_virtual_ptr_dispatch + +namespace test_shared_virtual_ptr_dispatch { + +BOOST_AUTO_TEST_CASE_TEMPLATE( + test_virtual_ptr_dispatch, Policy, policy_types<__COUNTER__>) { + + BOOST_OPENMETHOD_REGISTER( + use_classes); + + using poke = method< + BOOST_OPENMETHOD_NAME(poke)(shared_virtual_ptr), + std::string, Policy>; + + BOOST_OPENMETHOD_REGISTER( + typename poke::template override< + poke_bear>>); + + using fight = method< + BOOST_OPENMETHOD_NAME(fight)( + shared_virtual_ptr, + shared_virtual_ptr, + shared_virtual_ptr), + std::string, Policy>; + + BOOST_OPENMETHOD_REGISTER( + typename fight::template override, + shared_virtual_ptr, + shared_virtual_ptr>>); + + initialize(); + + auto bear = make_shared_virtual(); + auto warrior = make_shared_virtual(); + auto axe = make_shared_virtual(); + + BOOST_TEST(poke::fn(bear) == "growl"); + + BOOST_TEST(fight::fn(warrior, axe, bear) == "kill bear"); +} + +} // namespace test_shared_virtual_ptr_dispatch diff --git a/test/test_virtual_ptr_basic.cpp b/test/test_virtual_ptr_basic.cpp new file mode 100644 index 0000000..534a2c2 --- /dev/null +++ b/test/test_virtual_ptr_basic.cpp @@ -0,0 +1,227 @@ +// Copyright (c) 2018-2025 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) + +#include +#include +#include +#include + +#include +#include +#include + +#define BOOST_TEST_MODULE openmethod +#include +#include + +#include "test_util.hpp" + +using boost::mp11::mp_list; +using std::cout; +using namespace boost::openmethod; +using namespace boost::openmethod::detail; + +struct base { + virtual ~base() { + } +}; + +struct a : base {}; +struct b : base {}; +struct c : base {}; +struct d : base {}; +struct e : base {}; +struct f : base {}; + +static_assert( + std::is_same_v< + virtual_types>, b, virtual_>>>, + mp_list, std::shared_ptr>>); + +static_assert(std::is_same_v< + overrider_virtual_types< + mp_list, b, virtual_>, mp_list, + default_policy>, + mp_list>); + +static_assert( + std::is_same_v, default_policy>, a>); + +static_assert(std::is_same_v< + virtual_traits, default_policy>::virtual_type, a>); + +static_assert(std::is_same_v< + select_overrider_virtual_type_aux< + virtual_ptr, virtual_ptr, default_policy>::type, + a>); + +static_assert(std::is_same_v< + overrider_virtual_types< + mp_list, b, virtual_ptr>, + mp_list, e, virtual_ptr>, default_policy>, + mp_list>); + +static_assert( + std::is_same_v< + overrider_virtual_types< + mp_list&, b, const virtual_ptr&>, + mp_list&, e, const virtual_ptr&>, + default_policy>, + mp_list>); + +static_assert( + std::is_same_v< + overrider_virtual_types< + mp_list< + virtual_>, b, virtual_>>, + mp_list, e, std::shared_ptr>, default_policy>, + mp_list>); + +namespace using_polymorphic_classes { + +struct Animal { + virtual ~Animal() { + } +}; + +struct Dog : Animal {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog); + +namespace BOOST_OPENMETHOD_GENSYM { + +BOOST_OPENMETHOD(poke, (const virtual_ptr&, std::ostream&), void); + +BOOST_OPENMETHOD_OVERRIDE( + poke, (const virtual_ptr&, std::ostream& os), void) { + os << "bark"; +} + +static_assert(sizeof(virtual_ptr) == 2 * sizeof(void*)); + +BOOST_AUTO_TEST_CASE(test_virtual_ptr_by_ref) { + boost::openmethod::initialize(); + + { + boost::test_tools::output_test_stream os; + Dog dog; + virtual_ptr vptr(dog); + poke(vptr, os); + } + + { + // Using deduction guide. + boost::test_tools::output_test_stream os; + Animal&& animal = Dog(); + auto vptr = virtual_ptr(animal); + poke(vptr, os); + BOOST_CHECK(os.is_equal("bark")); + } + + { + // Using conversion ctor. + boost::test_tools::output_test_stream os; + Animal&& animal = Dog(); + poke(animal, os); + BOOST_CHECK(os.is_equal("bark")); + } +} + +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +BOOST_OPENMETHOD(poke, (virtual_ptr, std::ostream&), void); + +BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr, std::ostream& os), void) { + os << "bark"; +} + +BOOST_AUTO_TEST_CASE(test_virtual_shared_by_value) { + boost::openmethod::initialize(); + + { + boost::test_tools::output_test_stream os; + shared_virtual_ptr animal = make_shared_virtual(); + poke(animal, os); + BOOST_CHECK(os.is_equal("bark")); + } +} + +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +BOOST_OPENMETHOD( + poke, (const shared_virtual_ptr&, std::ostream&), void); + +BOOST_OPENMETHOD_OVERRIDE( + poke, (const shared_virtual_ptr&, std::ostream& os), void) { + os << "bark"; +} + +BOOST_AUTO_TEST_CASE(test_virtual_shared_by_const_reference) { + boost::openmethod::initialize(); + + { + boost::test_tools::output_test_stream os; + shared_virtual_ptr animal = make_shared_virtual(); + poke(animal, os); + BOOST_CHECK(os.is_equal("bark")); + } +} + +} // namespace BOOST_OPENMETHOD_GENSYM + +namespace BOOST_OPENMETHOD_GENSYM { + +BOOST_OPENMETHOD(poke, (unique_virtual_ptr, std::ostream&), void); + +BOOST_OPENMETHOD_OVERRIDE( + poke, (unique_virtual_ptr, std::ostream& os), void) { + os << "bark"; +} + +BOOST_AUTO_TEST_CASE(test_virtual_unique) { + boost::openmethod::initialize(); + + { + boost::test_tools::output_test_stream os; + poke(make_unique_virtual(), os); + BOOST_CHECK(os.is_equal("bark")); + } +} + +} // namespace BOOST_OPENMETHOD_GENSYM +} // namespace using_polymorphic_classes + +namespace using_non_polymorphic_classes { + +struct Animal {}; + +struct Dog : Animal {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog); + +BOOST_OPENMETHOD(poke, (virtual_ptr, std::ostream&), void); + +BOOST_OPENMETHOD_OVERRIDE(poke, (virtual_ptr, std::ostream& os), void) { + os << "bark"; +} + +BOOST_AUTO_TEST_CASE(test_virtual_ptr_non_polymorphic) { + boost::openmethod::initialize(); + + { + boost::test_tools::output_test_stream os; + Dog dog; + auto vptr = virtual_ptr::final(dog); + poke(vptr, os); + BOOST_CHECK(os.is_equal("bark")); + } +} + +} // namespace using_non_polymorphic_classes diff --git a/test/test_virtual_ptr_state.cpp b/test/test_virtual_ptr_state.cpp new file mode 100644 index 0000000..21dbd8d --- /dev/null +++ b/test/test_virtual_ptr_state.cpp @@ -0,0 +1,403 @@ +// Copyright (c) 2018-2025 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) + +#include +#include +#include + +#define BOOST_TEST_MODULE openmethod +#include + +#include +#include + +#include +#include +#include +#include +#include + +using namespace boost::openmethod; +using namespace boost::openmethod::policies; + +struct Animal { + virtual ~Animal() { + } + + Animal() = default; + Animal(const Animal&) = delete; + + int age; +}; + +struct Cat : virtual Animal {}; + +struct Dog : virtual Animal {}; + +template +void init_test() { + BOOST_OPENMETHOD_REGISTER(use_classes); + struct id; + (void)&method), void, Policy>::fn; + boost::openmethod::initialize(); +} + +struct direct_vector_policy : default_policy::fork {}; + +struct indirect_vector_policy + : default_policy::fork::add::replace< + vptr, vptr_vector> {}; + +struct direct_map_policy : default_policy::fork::replace< + vptr, vptr_map> {}; + +struct indirect_map_policy + : default_policy::fork::add::replace< + vptr, vptr_map> {}; + +using test_policies = boost::mp11::mp_list< + direct_vector_policy, indirect_vector_policy, direct_map_policy, + indirect_map_policy>; + +BOOST_AUTO_TEST_CASE_TEMPLATE(virtual_ptr_ctors, Policy, test_policies) { + static_assert(std::is_same_v< + typename virtual_ptr::element_type, Animal>); + static_assert(std::is_same_v< + decltype(std::declval>().get()), + Animal*>); + static_assert(!virtual_ptr::is_smart_ptr); + static_assert(!virtual_ptr::is_smart_ptr); + static_assert( + std::is_same_v< + decltype(*std::declval>()), Animal&>); + static_assert(!std::is_constructible_v, Dog>); + + init_test(); + + { + Dog dog; + + { + virtual_ptr p(dog); + BOOST_TEST(p.vptr() != nullptr); + BOOST_TEST(p.vptr() == Policy::template static_vptr); + virtual_ptr copy(p); + virtual_ptr base(p); + virtual_ptr const_copy(p); + virtual_ptr const_base_copy(p); + } + + { + const auto p = virtual_ptr(dog); + virtual_ptr const_copy(p); + virtual_ptr const_base_copy(p); + } + + { + auto p = virtual_ptr(dog); + } + } +} + +template< + template class smart_ptr, + template class other_smart_ptr, class Policy> +struct check_smart_ctors { + // construction + + // a virtual_ptr can be constructed from a smart_ptr (same class) + static_assert(std::is_constructible_v< + virtual_ptr, Policy>, smart_ptr>); + + // a virtual_ptr to const can be constructed from smart_ptr (same class) + static_assert( + std::is_constructible_v< + virtual_ptr, Policy>, const smart_ptr&>); + + static_assert( + std::is_constructible_v< + virtual_ptr, Policy>, smart_ptr>); + + // a virtual_ptr can be constructed from a smart_ptr (derived class) + static_assert(std::is_constructible_v< + virtual_ptr, Policy>, smart_ptr>); + + // a virtual_ptr to const can be constructed from a smart_ptr (derived class) + static_assert( + std::is_constructible_v< + virtual_ptr, Policy>, smart_ptr>); + + // a virtual_ptr cannot be constructed from a smart_ptr to a different class + static_assert(!std::is_constructible_v< + virtual_ptr, Policy>, smart_ptr>); + + // a virtual_ptr cannot be constructed from const smart_ptr + static_assert( + !std::is_constructible_v< + virtual_ptr, Policy>, smart_ptr>); + + // policies must be the same + static_assert(!std::is_constructible_v< + virtual_ptr, policies::debug>, + virtual_ptr, policies::release>>); + + // move constructible + static_assert( + std::is_move_constructible_v, Policy>>); + + // a smart virtual_ptr cannot be constructed from a plain reference or + // pointer + static_assert(!std::is_constructible_v< + virtual_ptr, Policy>, Animal>); + static_assert(!std::is_constructible_v< + virtual_ptr, Policy>, Animal*>); + + // the smart pointer must be the same (e.g. both shared_ptr) + static_assert(!std::is_constructible_v< + virtual_ptr, Policy>, + virtual_ptr, Policy>>); + + // a smart virtual_ptr converts to a plain one + static_assert(std::is_constructible_v< + virtual_ptr, + virtual_ptr, Policy>>); + + // but not the other way around + static_assert(!std::is_constructible_v< + virtual_ptr, Policy>, + virtual_ptr>); + + // --------------------- + // test other properties + + static_assert(virtual_ptr, Policy>::is_smart_ptr); + static_assert(virtual_ptr, Policy>::is_smart_ptr); + + static_assert(std::is_same_v< + typename virtual_ptr, Policy>::element_type, + Animal>); + + static_assert( + std::is_same_v< + decltype(std::declval, Policy>>() + .get()), + Animal*>); + + static_assert( + std::is_same_v< + decltype(*std::declval, Policy>>()), + Animal&>); + + static_assert( + std::is_same_v< + decltype(std::declval, Policy>>() + .pointer()), + const smart_ptr&>); + + static_assert( + std::is_same_v< + decltype(*std::declval, Policy>>()), + Animal&>); +}; + +template struct check_smart_ctors< + std::shared_ptr, std::unique_ptr, direct_vector_policy>; + +template struct check_smart_ctors< + std::unique_ptr, std::shared_ptr, direct_vector_policy>; + +BOOST_AUTO_TEST_CASE_TEMPLATE(shared_virtual_ptr_ctors, Policy, test_policies) { + { + init_test(); + + auto dog = std::make_shared(); + + { + shared_virtual_ptr ptr(dog); + BOOST_TEST((ptr.get() == dog.get())); + BOOST_TEST(ptr.pointer() == dog); + BOOST_TEST(ptr.vptr() == Policy::template static_vptr); + + shared_virtual_ptr copy(ptr); + BOOST_TEST((copy.get() == dog.get())); + BOOST_TEST(copy.pointer() == dog); + BOOST_TEST(copy.vptr() == Policy::template static_vptr); + + shared_virtual_ptr base(ptr); + BOOST_TEST((base.get() == dog.get())); + + shared_virtual_ptr downcast = + base.template cast(); + BOOST_TEST((downcast.get() == dog.get())); + BOOST_TEST(base.vptr() == Policy::template static_vptr); + + shared_virtual_ptr const_copy(ptr); + shared_virtual_ptr base_const_copy(ptr); + + shared_virtual_ptr move_ptr(std::move(ptr)); + BOOST_TEST(ptr.pointer().get() == nullptr); + BOOST_TEST(move_ptr.pointer().get() == dog.get()); + } + + { + shared_virtual_ptr ptr(std::make_shared()); + virtual_ptr dumb_vptr(ptr); + BOOST_TEST(dumb_vptr.get() == ptr.get()); + BOOST_TEST(dumb_vptr.vptr() == ptr.vptr()); + } + + { + shared_virtual_ptr ptr(std::make_shared()); + virtual_ptr dumb_vptr(ptr); + BOOST_TEST(dumb_vptr.get() == ptr.get()); + BOOST_TEST(dumb_vptr.vptr() == ptr.vptr()); + } + + { + shared_virtual_ptr ptr(dog); + shared_virtual_ptr move_const_ptr( + std::move(ptr)); + BOOST_TEST(ptr.pointer().get() == nullptr); + BOOST_TEST(move_const_ptr.pointer().get() == dog.get()); + } + + { + shared_virtual_ptr ptr(dog); + } + + { + // should not compile: + // unique_virtual_ptr unique_dog(dog); + } + } + + { + auto dog = std::make_shared(); + + shared_virtual_ptr ptr(dog); + BOOST_TEST(ptr.pointer().get() == dog.get()); + BOOST_TEST(ptr.vptr() == Policy::template static_vptr); + + shared_virtual_ptr copy(ptr); + shared_virtual_ptr base(ptr); + } +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(unique_virtual_ptr_ctors, Policy, test_policies) { + init_test(); + + { + // a unique_virtual_ptr can be created from a std::unique_ptr + std::unique_ptr is_smart_ptr = std::make_unique(); + auto dumb_ptr = is_smart_ptr.get(); + unique_virtual_ptr virtual_smart_ptr( + std::move(is_smart_ptr)); + + // and ownership is transferred + BOOST_TEST(is_smart_ptr.get() == nullptr); + BOOST_TEST(virtual_smart_ptr.get() == dumb_ptr); + } + + { + // a virtual_ptr can be constructed from a unique_virtual_ptr + auto virtual_smart_ptr = make_unique_virtual(); + auto dumb_ptr = virtual_smart_ptr.get(); + virtual_ptr virtual_dumb_ptr = virtual_smart_ptr; + BOOST_TEST(virtual_dumb_ptr.get() == dumb_ptr); + // ownership is not transferred + BOOST_TEST(virtual_smart_ptr.get() == dumb_ptr); + } + + { + // unique_virtual_ptr can be moved + auto unique = make_unique_virtual(); + Dog* dumb_ptr = unique.get(); + unique_virtual_ptr unique_moved = std::move(unique); + BOOST_TEST(unique.get() == nullptr); + BOOST_TEST(unique_moved.get() == dumb_ptr); + } + + { + unique_virtual_ptr base(std::make_unique()); + auto p = base.get(); + unique_virtual_ptr downcast = + std::move(base).template cast(); + BOOST_TEST((downcast.get() == p)); + BOOST_TEST((base.get() == nullptr)); + BOOST_TEST(downcast.vptr() == Policy::template static_vptr); + } +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(indirect_virtual_ptr, Policy, test_policies) { + BOOST_TEST_MESSAGE( + "Policy = " << boost::core::demangle(typeid(Policy).name())); + + init_test(); + + Dog dog; + virtual_ptr p(dog); + + BOOST_TEST_MESSAGE("After first call to initialize:"); + BOOST_TEST_MESSAGE("p.vptr() = " << p.vptr()); + BOOST_TEST_MESSAGE( + "static_vptr = " << Policy::template static_vptr); + BOOST_TEST(p.vptr() == Policy::template static_vptr); + + // Add a class, to make sure dispatch data is not re-constructed in the same + // place with the same values: + struct Cat : Animal {}; + BOOST_OPENMETHOD_CLASSES(Animal, Cat, Policy); + + init_test(); + + BOOST_TEST_MESSAGE("After second call to initialize:"); + BOOST_TEST_MESSAGE("p.vptr() = " << p.vptr()); + BOOST_TEST_MESSAGE( + "static_vptr = " << Policy::template static_vptr); + + if constexpr (Policy::template has_facet) { + BOOST_TEST(p.vptr() == Policy::template static_vptr); + } else { + BOOST_TEST(p.vptr() != Policy::template static_vptr); + } +} + +BOOST_AUTO_TEST_CASE(virtual_ptr_final_error) { + auto prev_handler = default_policy::set_error_handler( + [](const default_policy::error_variant& ev) { + if (auto error = std::get_if(&ev)) { + static_assert(std::is_same_v< + decltype(error), const type_mismatch_error*>); + throw *error; + } + }); + + init_test(); + bool threw = false; + + try { + Dog snoopy; + Animal& animal = snoopy; + virtual_ptr::final(animal); + } catch (const type_mismatch_error& error) { + default_policy::set_error_handler(prev_handler); + BOOST_TEST(error.type == reinterpret_cast(&typeid(Dog))); + threw = true; + } catch (...) { + default_policy::set_error_handler(prev_handler); + BOOST_FAIL("wrong exception"); + return; + } + + if constexpr (default_policy::has_facet) { + if (!threw) { + BOOST_FAIL("should have thrown"); + } + } else { + if (threw) { + BOOST_FAIL("should not have thrown"); + } + } +}