diff --git a/doc/modules/ROOT/examples/CMakeLists.txt b/doc/modules/ROOT/examples/CMakeLists.txt index 31bb86f..108c9fc 100644 --- a/doc/modules/ROOT/examples/CMakeLists.txt +++ b/doc/modules/ROOT/examples/CMakeLists.txt @@ -26,8 +26,17 @@ foreach (cpp ${cpp_files}) add_dependencies(tests ${stem}) endforeach() -add_subdirectory(headers_namespaces) - if (NOT WIN32) add_subdirectory(shared_libs) endif() + +file(GLOB subdirs "rolex/*") + +foreach (subdir ${subdirs}) + string(REGEX REPLACE ".*/" "" subex ${subdir}) + file(GLOB cpp_files "${subdir}/*.cpp") + add_executable("rolex_${subex}" ${cpp_files}) + target_link_libraries("rolex_${subex}" PUBLIC Boost::openmethod) + add_test(NAME "rolex_${subex}" COMMAND "rolex_${subex}") + add_dependencies(tests "rolex_${subex}") +endforeach() diff --git a/doc/modules/ROOT/examples/ast.cpp b/doc/modules/ROOT/examples/ast.cpp index 8f2e843..402cabe 100644 --- a/doc/modules/ROOT/examples/ast.cpp +++ b/doc/modules/ROOT/examples/ast.cpp @@ -29,6 +29,12 @@ struct Times : Node { const Node& left; const Node& right; }; +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +// library code +// ============================================================================= +// application code +// vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv + #include #include #include @@ -67,6 +73,7 @@ int main() { postfix(e, std::cout); std::cout << " = " << e.value() << "\n"; // 2 3 + 4 * = 20 } +// end::content[] void call_via_ref(const Node& node, std::ostream& os) { postfix(node, os); diff --git a/doc/modules/ROOT/examples/ast_unique_ptr.cpp b/doc/modules/ROOT/examples/ast_unique_ptr.cpp index 8052752..a10d201 100644 --- a/doc/modules/ROOT/examples/ast_unique_ptr.cpp +++ b/doc/modules/ROOT/examples/ast_unique_ptr.cpp @@ -11,9 +11,6 @@ using namespace boost::openmethod::aliases; -// NOTE: unique_virtual_ptr is an alias for -// virtual_ptr> - struct Node { virtual ~Node() {} virtual int value() const = 0; @@ -70,11 +67,13 @@ BOOST_OPENMETHOD_CLASSES(Node, Variable, Plus, Times); int main() { boost::openmethod::initialize(); + auto a = std::make_unique(2); auto b = std::make_unique(3); auto c = std::make_unique(4); auto d = make_unique_virtual(std::move(a), std::move(b)); auto e = make_unique_virtual(std::move(d), std::move(c)); + postfix(e, std::cout); std::cout << " = " << e->value() << "\n"; // 2 3 + 4 * = 20 } diff --git a/doc/modules/ROOT/examples/friendship.cpp b/doc/modules/ROOT/examples/friendship.cpp index c2e3cfe..69d8bcf 100644 --- a/doc/modules/ROOT/examples/friendship.cpp +++ b/doc/modules/ROOT/examples/friendship.cpp @@ -39,7 +39,7 @@ class Animal { class Cat; class Dog; -template struct BOOST_OPENMETHOD_OVERRIDERS(poke); +template struct BOOST_OPENMETHOD_OVERRIDERS(poke); class Animal { // ... diff --git a/doc/modules/ROOT/examples/friendship_across_namespaces.cpp b/doc/modules/ROOT/examples/friendship_across_namespaces.cpp index ac6455f..bbe5373 100644 --- a/doc/modules/ROOT/examples/friendship_across_namespaces.cpp +++ b/doc/modules/ROOT/examples/friendship_across_namespaces.cpp @@ -43,7 +43,7 @@ BOOST_OPENMETHOD(poke, (std::ostream&, virtual_ptr), void); namespace pets { struct Cat; struct Dog; -template struct BOOST_OPENMETHOD_OVERRIDERS(poke); +template struct BOOST_OPENMETHOD_OVERRIDERS(poke); } // namespace pets namespace core { diff --git a/doc/modules/ROOT/examples/rolex/1/employee.cpp b/doc/modules/ROOT/examples/rolex/1/employee.cpp new file mode 100644 index 0000000..a2dec09 --- /dev/null +++ b/doc/modules/ROOT/examples/rolex/1/employee.cpp @@ -0,0 +1,16 @@ +// 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::content[] +#include "roles.hpp" +#include + +BOOST_OPENMETHOD_OVERRIDE( + pay, (boost::openmethod::virtual_ptr), double) { + return 5000.0; +} + +BOOST_OPENMETHOD_CLASSES(Employee) +// end::content[] diff --git a/doc/modules/ROOT/examples/rolex/1/main.cpp b/doc/modules/ROOT/examples/rolex/1/main.cpp new file mode 100644 index 0000000..32dbcb9 --- /dev/null +++ b/doc/modules/ROOT/examples/rolex/1/main.cpp @@ -0,0 +1,23 @@ +// 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::content[] +// main.cpp + +#include "roles.hpp" +#include + +#include + +int main() { + boost::openmethod::initialize(); + + Employee bill; + Salesman bob; bob.sales = 100'000.0; + + std::cout << "pay bill: $" << pay(bill) << "\n"; // pay bill: $5000 + std::cout << "pay bob: $" << pay(bob) << "\n"; // pay bob: $10000 +} +// end::content[] diff --git a/doc/modules/ROOT/examples/rolex/1/roles.hpp b/doc/modules/ROOT/examples/rolex/1/roles.hpp new file mode 100644 index 0000000..45946ba --- /dev/null +++ b/doc/modules/ROOT/examples/rolex/1/roles.hpp @@ -0,0 +1,23 @@ +// 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::content[] +// roles.hpp + +#ifndef ROLES_HPP +#define ROLES_HPP + +#include + +struct Employee { virtual ~Employee() = default; }; + +struct Salesman : Employee { + double sales = 0.0; +}; + +BOOST_OPENMETHOD(pay, (boost::openmethod::virtual_ptr), double); + +#endif // ROLES_HPP +// end::content[] diff --git a/doc/modules/ROOT/examples/rolex/1/salesman.cpp b/doc/modules/ROOT/examples/rolex/1/salesman.cpp new file mode 100644 index 0000000..8eef162 --- /dev/null +++ b/doc/modules/ROOT/examples/rolex/1/salesman.cpp @@ -0,0 +1,17 @@ +// 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::content[] +#include "roles.hpp" +#include + +BOOST_OPENMETHOD_OVERRIDE( + pay, (boost::openmethod::virtual_ptr emp), double) { + return next(emp) + emp->sales * 0.05; // base + commission +} + +BOOST_OPENMETHOD_CLASSES(Employee, Salesman) + +// end::content[] diff --git a/doc/modules/ROOT/examples/rolex/2/employee.cpp b/doc/modules/ROOT/examples/rolex/2/employee.cpp new file mode 100644 index 0000000..6a79542 --- /dev/null +++ b/doc/modules/ROOT/examples/rolex/2/employee.cpp @@ -0,0 +1,16 @@ +// 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::content[] +#include "roles.hpp" +#include + +BOOST_OPENMETHOD_DEFINE_OVERRIDER( + pay, (boost::openmethod::virtual_ptr), double) { + return 5000.0; +} + +BOOST_OPENMETHOD_CLASSES(Employee) +// end::content[] diff --git a/doc/modules/ROOT/examples/rolex/2/main.cpp b/doc/modules/ROOT/examples/rolex/2/main.cpp new file mode 100644 index 0000000..32dbcb9 --- /dev/null +++ b/doc/modules/ROOT/examples/rolex/2/main.cpp @@ -0,0 +1,23 @@ +// 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::content[] +// main.cpp + +#include "roles.hpp" +#include + +#include + +int main() { + boost::openmethod::initialize(); + + Employee bill; + Salesman bob; bob.sales = 100'000.0; + + std::cout << "pay bill: $" << pay(bill) << "\n"; // pay bill: $5000 + std::cout << "pay bob: $" << pay(bob) << "\n"; // pay bob: $10000 +} +// end::content[] diff --git a/doc/modules/ROOT/examples/rolex/2/roles.hpp b/doc/modules/ROOT/examples/rolex/2/roles.hpp new file mode 100644 index 0000000..5865163 --- /dev/null +++ b/doc/modules/ROOT/examples/rolex/2/roles.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) + +// tag::content[] +// roles.hpp + +#ifndef ROLES_HPP +#define ROLES_HPP + +#include + +struct Employee { virtual ~Employee() = default; }; + +struct Salesman : Employee { + double sales = 0.0; +}; + +BOOST_OPENMETHOD(pay, (boost::openmethod::virtual_ptr), double); + +BOOST_OPENMETHOD_DECLARE_OVERRIDER( + pay, (boost::openmethod::virtual_ptr), double); + + +#endif // ROLES_HPP +// end::content[] diff --git a/doc/modules/ROOT/examples/rolex/2/salesman.cpp b/doc/modules/ROOT/examples/rolex/2/salesman.cpp new file mode 100644 index 0000000..6b9df0a --- /dev/null +++ b/doc/modules/ROOT/examples/rolex/2/salesman.cpp @@ -0,0 +1,19 @@ +// 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 "roles.hpp" +#include + +// tag::content[] +BOOST_OPENMETHOD_OVERRIDE( + pay, (boost::openmethod::virtual_ptr emp), double) { + return BOOST_OPENMETHOD_OVERRIDER( + pay, (boost::openmethod::virtual_ptr emp), + double)::fn(emp) + + emp->sales * 0.05; // base + commission +} +// end::content[] + +BOOST_OPENMETHOD_CLASSES(Employee, Salesman) diff --git a/doc/modules/ROOT/examples/rolex/3/employee.cpp b/doc/modules/ROOT/examples/rolex/3/employee.cpp new file mode 100644 index 0000000..4fbb428 --- /dev/null +++ b/doc/modules/ROOT/examples/rolex/3/employee.cpp @@ -0,0 +1,11 @@ +// 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::content[] +#include "roles.hpp" +#include + +BOOST_OPENMETHOD_CLASSES(Employee) +// end::content[] diff --git a/doc/modules/ROOT/examples/rolex/3/main.cpp b/doc/modules/ROOT/examples/rolex/3/main.cpp new file mode 100644 index 0000000..32dbcb9 --- /dev/null +++ b/doc/modules/ROOT/examples/rolex/3/main.cpp @@ -0,0 +1,23 @@ +// 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::content[] +// main.cpp + +#include "roles.hpp" +#include + +#include + +int main() { + boost::openmethod::initialize(); + + Employee bill; + Salesman bob; bob.sales = 100'000.0; + + std::cout << "pay bill: $" << pay(bill) << "\n"; // pay bill: $5000 + std::cout << "pay bob: $" << pay(bob) << "\n"; // pay bob: $10000 +} +// end::content[] diff --git a/doc/modules/ROOT/examples/rolex/3/roles.hpp b/doc/modules/ROOT/examples/rolex/3/roles.hpp new file mode 100644 index 0000000..1717658 --- /dev/null +++ b/doc/modules/ROOT/examples/rolex/3/roles.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) + +// tag::content[] +// roles.hpp + +#ifndef ROLES_HPP +#define ROLES_HPP + +#include + +struct Employee { virtual ~Employee() = default; }; + +struct Salesman : Employee { + double sales = 0.0; +}; + +BOOST_OPENMETHOD(pay, (boost::openmethod::virtual_ptr), double); + +BOOST_OPENMETHOD_INLINE_OVERRIDE( + pay, (boost::openmethod::virtual_ptr), double) { + return 5000.0; +} + +#endif // ROLES_HPP +// end::content[] diff --git a/doc/modules/ROOT/examples/rolex/3/salesman.cpp b/doc/modules/ROOT/examples/rolex/3/salesman.cpp new file mode 100644 index 0000000..6b9df0a --- /dev/null +++ b/doc/modules/ROOT/examples/rolex/3/salesman.cpp @@ -0,0 +1,19 @@ +// 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 "roles.hpp" +#include + +// tag::content[] +BOOST_OPENMETHOD_OVERRIDE( + pay, (boost::openmethod::virtual_ptr emp), double) { + return BOOST_OPENMETHOD_OVERRIDER( + pay, (boost::openmethod::virtual_ptr emp), + double)::fn(emp) + + emp->sales * 0.05; // base + commission +} +// end::content[] + +BOOST_OPENMETHOD_CLASSES(Employee, Salesman) diff --git a/doc/modules/ROOT/examples/rolex/4/employee.cpp b/doc/modules/ROOT/examples/rolex/4/employee.cpp new file mode 100644 index 0000000..8022307 --- /dev/null +++ b/doc/modules/ROOT/examples/rolex/4/employee.cpp @@ -0,0 +1,11 @@ +// 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::content[] +#include "roles.hpp" +#include + +BOOST_OPENMETHOD_CLASSES(employees::Employee) +// end::content[] diff --git a/doc/modules/ROOT/examples/rolex/4/main.cpp b/doc/modules/ROOT/examples/rolex/4/main.cpp new file mode 100644 index 0000000..12f4189 --- /dev/null +++ b/doc/modules/ROOT/examples/rolex/4/main.cpp @@ -0,0 +1,23 @@ +// 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::content[] +// main.cpp + +#include "roles.hpp" +#include + +#include + +int main() { + boost::openmethod::initialize(); + + employees::Employee bill; + sales::Salesman bob; bob.sales = 100'000.0; + + std::cout << "pay bill: $" << pay(bill) << "\n"; // pay bill: $5000 + std::cout << "pay bob: $" << pay(bob) << "\n"; // pay bob: $10000 +} +// end::content[] diff --git a/doc/modules/ROOT/examples/rolex/4/roles.hpp b/doc/modules/ROOT/examples/rolex/4/roles.hpp new file mode 100644 index 0000000..c8a48f2 --- /dev/null +++ b/doc/modules/ROOT/examples/rolex/4/roles.hpp @@ -0,0 +1,37 @@ +// 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::content[] +// roles.hpp + +#ifndef ROLES_HPP +#define ROLES_HPP + +#include + +namespace employees { + +struct Employee { + virtual ~Employee() = default; +}; + +BOOST_OPENMETHOD(pay, (boost::openmethod::virtual_ptr), double); + +BOOST_OPENMETHOD_INLINE_OVERRIDE( + pay, (boost::openmethod::virtual_ptr), double) { + return 5000.0; +} + +} + +namespace sales { + +struct Salesman : employees::Employee { + double sales = 0.0; +}; + +} // namespace sales +#endif // ROLES_HPP +// end::content[] diff --git a/doc/modules/ROOT/examples/rolex/4/salesman.cpp b/doc/modules/ROOT/examples/rolex/4/salesman.cpp new file mode 100644 index 0000000..e1a2d7b --- /dev/null +++ b/doc/modules/ROOT/examples/rolex/4/salesman.cpp @@ -0,0 +1,23 @@ +// 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 "roles.hpp" +#include + +// tag::content[] +namespace sales { + +BOOST_OPENMETHOD_OVERRIDE( + pay, (boost::openmethod::virtual_ptr emp), double) { + return employees::BOOST_OPENMETHOD_OVERRIDER( + pay, (boost::openmethod::virtual_ptr emp), + double)::fn(emp) + + emp->sales * 0.05; // base + commission +} + +BOOST_OPENMETHOD_CLASSES(employees::Employee, Salesman) + +} // namespace sales +// end::content[] diff --git a/doc/modules/ROOT/examples/rolex/5/main.cpp b/doc/modules/ROOT/examples/rolex/5/main.cpp new file mode 100644 index 0000000..102ce12 --- /dev/null +++ b/doc/modules/ROOT/examples/rolex/5/main.cpp @@ -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) + +// tag::content[] +#include +#include +#include + +class Payroll; + +struct Employee { + virtual ~Employee() = default; +}; + +struct Salesman : Employee { + double sales = 0.0; +}; + +// tag::pay[] +BOOST_OPENMETHOD( + pay, (Payroll & payroll, boost::openmethod::virtual_ptr), + double); +// end::pay[] + +// tag::payroll[] +class Payroll { + public: + double balance() const { + return balance_; + } + + private: + double balance_ = 1'000'000.0; + + void update_balance(double amount) { + // throw if balance would become negative + balance_ += amount; + } + + friend BOOST_OPENMETHOD_OVERRIDER( + pay, (Payroll & payroll, boost::openmethod::virtual_ptr), + double); + friend BOOST_OPENMETHOD_OVERRIDER( + pay, + (Payroll & payroll, boost::openmethod::virtual_ptr), + double); +}; +// end::payroll[] + +// tag::overriders[] +BOOST_OPENMETHOD_OVERRIDE( + pay, (Payroll & payroll, boost::openmethod::virtual_ptr), + double) { + double pay = 5000.0; + payroll.update_balance(-pay); + + return pay; +} + +BOOST_OPENMETHOD_OVERRIDE( + pay, + (Payroll & payroll, boost::openmethod::virtual_ptr emp), + double) { + double base = next(payroll, emp); + double commission = emp->sales * 0.05; + payroll.update_balance(-commission); + + return base + commission; +} + +// ...and let's not forget to register the classes +BOOST_OPENMETHOD_CLASSES(Employee, Salesman) +// end::overriders[] + +// tag::main[] +int main() { + boost::openmethod::initialize(); + + Payroll payroll; + Employee bill; + Salesman bob; + bob.sales = 100'000.0; + + std::cout << "pay bill: $" << pay(payroll, bill) << "\n"; // $5000 + std::cout << "pay bob: $" << pay(payroll, bob) << "\n"; // 10000 + std::cout << "remaining balance: $" << payroll.balance() << "\n"; // $985000 +} +// end::main[] diff --git a/doc/modules/ROOT/examples/rolex/6/main.cpp b/doc/modules/ROOT/examples/rolex/6/main.cpp new file mode 100644 index 0000000..cceccc4 --- /dev/null +++ b/doc/modules/ROOT/examples/rolex/6/main.cpp @@ -0,0 +1,85 @@ +// 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::content[] +#include +#include +#include + +class Payroll; + +struct Employee { + virtual ~Employee() = default; +}; + +struct Salesman : Employee { + double sales = 0.0; +}; + +// tag::pay[] +BOOST_OPENMETHOD( + pay, (Payroll & payroll, boost::openmethod::virtual_ptr), + double); +// end::pay[] + +// tag::payroll[] +class Payroll { + public: + double balance() const { + return balance_; + } + + private: + double balance_ = 1'000'000.0; + + void update_balance(double amount) { + // throw if balance would become negative + balance_ += amount; + } + + template + friend struct BOOST_OPENMETHOD_OVERRIDERS(pay); +}; +// end::payroll[] + +// tag::overriders[] +BOOST_OPENMETHOD_OVERRIDE( + pay, (Payroll & payroll, boost::openmethod::virtual_ptr), + double) { + double pay = 5000.0; + payroll.update_balance(-pay); + + return pay; +} + +BOOST_OPENMETHOD_OVERRIDE( + pay, + (Payroll & payroll, boost::openmethod::virtual_ptr emp), + double) { + double base = next(payroll, emp); + double commission = emp->sales * 0.05; + payroll.update_balance(-commission); + + return base + commission; +} + +// ...and let's not forget to register the classes +BOOST_OPENMETHOD_CLASSES(Employee, Salesman) +// end::overriders[] + +// tag::main[] +int main() { + boost::openmethod::initialize(); + + Payroll payroll; + Employee bill; + Salesman bob; + bob.sales = 100'000.0; + + std::cout << "pay bill: $" << pay(payroll, bill) << "\n"; // $5000 + std::cout << "pay bob: $" << pay(payroll, bob) << "\n"; // 10000 + std::cout << "remaining balance: $" << payroll.balance() << "\n"; // $985000 +} +// end::main[] diff --git a/doc/modules/ROOT/examples/rolex/7/main.cpp b/doc/modules/ROOT/examples/rolex/7/main.cpp new file mode 100644 index 0000000..68c30f7 --- /dev/null +++ b/doc/modules/ROOT/examples/rolex/7/main.cpp @@ -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) + +// tag::content[] +#include +#include +#include + +// tag::classes[] +struct Role { + virtual ~Role() = default; +}; + +struct Employee : Role {}; +struct Salesman : Employee {}; +struct Manager : Employee {}; +struct Founder : Role {}; + +struct Expense { + virtual ~Expense() { + } +}; + +struct Public : Expense {}; +struct Bus : Public {}; +struct Metro : Public {}; +struct Taxi : Expense {}; +struct Jet : Expense {}; +// end::classes[] + +BOOST_OPENMETHOD_CLASSES( + Role, Employee, Manager, Founder, Expense, Public, Bus, Metro, Taxi, Jet); + +// tag::approve[] +using boost::openmethod::virtual_ptr; + +BOOST_OPENMETHOD( + approve, (virtual_ptr, virtual_ptr, double), + bool); + +BOOST_OPENMETHOD_OVERRIDE( + approve, (virtual_ptr, virtual_ptr, double), + bool) { + return false; +} + +BOOST_OPENMETHOD_OVERRIDE( + approve, (virtual_ptr, virtual_ptr, double), + bool) { + return true; +} + +BOOST_OPENMETHOD_OVERRIDE( + approve, + (virtual_ptr, virtual_ptr, double amount), + bool) { + return amount < 100.0; +} + +BOOST_OPENMETHOD_OVERRIDE( + approve, (virtual_ptr, virtual_ptr, double), + bool) { + return true; +} + +// tag::main[] +int main() { + boost::openmethod::initialize(); + + Founder bill; + Employee bob; + Manager alice; + + Bus bus; + Taxi taxi; + Jet jet; + + std::cout << std::boolalpha; + std::cout << approve(bill, bus, 4.0) << "\n"; // true + std::cout << approve(bob, bus, 4.0) << "\n"; // true + std::cout << approve(bob, taxi, 36.0) << "\n"; // false + std::cout << approve(alice, taxi, 36.0) << "\n"; // true + std::cout << approve(alice, taxi, 2000.0) << "\n"; // false + std::cout << approve(bill, jet, 120'000.0) << "\n"; // true + std::cout << approve(bob, jet, 120'000.0) << "\n"; // false + std::cout << approve(alice, jet, 120'000.0) << "\n"; // false +} +// end::main[] diff --git a/doc/modules/ROOT/nav.adoc b/doc/modules/ROOT/nav.adoc index cf935c0..8edf8b7 100644 --- a/doc/modules/ROOT/nav.adoc +++ b/doc/modules/ROOT/nav.adoc @@ -2,7 +2,8 @@ * xref:basics.adoc[Basics] * xref:performance.adoc[Performance] * xref:smart_pointers.adoc[Smart Pointers] -* xref:headers_namespaces.adoc[Headers and Namespaces] +* xref:headers.adoc[Headers] +* xref:namespaces.adoc[Namespaces] * xref:friendship.adoc[Friendship] * xref:multiple_dispatch.adoc[Multiple Dispatch] * Advanced Features @@ -13,6 +14,6 @@ ** xref:custom_rtti.adoc[Custom RTTI] ** xref:shared_libraries.adoc[Shared Libraries] * Reference -** xref:headers.adoc[Headers] -** xref:macros.adoc[Macros] +** xref:ref_headers.adoc[Headers] +** xref:ref_macros.adoc[Macros] ** xref:reference:boost/openmethod.adoc[Namespace boost::openmethod] diff --git a/doc/modules/ROOT/pages/basics.adoc b/doc/modules/ROOT/pages/basics.adoc index dbb4e2c..8329c55 100644 --- a/doc/modules/ROOT/pages/basics.adoc +++ b/doc/modules/ROOT/pages/basics.adoc @@ -3,8 +3,8 @@ ## Basics An _open-method_ is a free-standing function that takes one or more _virtual_ -_parameters_. When it is called, it forwards to an _overrider_ selected by -examining the dynamic type of the virtual parameters. +_parameters_. When it is called, it forwards to an _overrider_ selected from a +set by examining the dynamic type of the virtual parameters. If this sounds like a virtual function, that's because because an open-method with one virtual parameter is equivalent to a virtual function - with a big @@ -69,8 +69,8 @@ constructors from plain references or pointers to an object, or from other There are two more things we need to do. -OpenMethod is a library, not a compiler. It needs to be aware of all the classes -that may be used as virtual parameters, and in method calls, and their +OpenMethod is a library, not a compiler. It needs to be made aware of all the +classes that may be used as virtual parameters, and in method calls, and their inheritance relationships. We do this using the xref:BOOST_OPENMETHOD_CLASSES.adoc[BOOST_OPENMETHOD_CLASSES] macro: @@ -83,9 +83,9 @@ direct base of a class must appear together with it in at least one call to `BOOST_OPENMETHOD_CLASSES`. This enables the library to deduce the complete inheritance lattice. -The constructs used in this example require the classes need to be polymorphic, -in the standard C++ sense, i.e. have at least one virtual function. The library -can also be used with non-polymorphic classes, with some restrictions. +The constructs used in this example require the classes to be polymorphic, in +the standard C++ sense, i.e. have at least one virtual function. The library can +also be used with non-polymorphic classes, with some restrictions. Finally, we need to call `boost::openmethod::initialize()` before the first call to an open-method. This builds the dispatch tables used during method calls. It diff --git a/doc/modules/ROOT/pages/friendship.adoc b/doc/modules/ROOT/pages/friendship.adoc index e445edf..7019b5d 100644 --- a/doc/modules/ROOT/pages/friendship.adoc +++ b/doc/modules/ROOT/pages/friendship.adoc @@ -1,31 +1,49 @@ +:example: ../examples/rolex ## Friendship -We can use overrider containers to grant friendship to a specific overrider, or -to all the overriders of a method. The name of the container template is -returned by `BOOST_OPENMETHOD_OVERRIDERS`. The template argument for a -specialization is the signature of the overrider. For example, the overrider of -`poke` for `Cat` is: +Note;; This section uses overrider containers, described in the +xref:headers.adoc[Headers] section. + +`friend` is a controversial feature. OpenMethod aims to interact well with +all of C++, as much as feasible for a library, and leave the user the choice of +using `friend`, or not. + +Let's consider yet another variation of the `pay` example. This time, we want to +update a `balance` variable in a `Payroll` class, when an employee is paid. Thus +we pass the payroll object to the `pay` method: [source,c++] ---- -BOOST_OPENMETHOD_OVERRIDERS(poke)< - void(std::ostream& os, virtual_ptr cat)>::fn; +include::{example}/5/main.cpp[tag=pay] ---- -We can thus grant friendship to all the overriders of `poke`: +`BOOST_OPENMETHOD` declares an overrider container for `pay` in the current +namespace, even though it does not define any overrider by itself. We can thus +name the individual address containers in `friend` declarations. But note that +at this point, the containers have not been specialized yet! In particular, the +`fn` member function does not exist yet. Instead, we declare friendship to the +container itself: [source,c++] ---- -include::example$friendship.cpp[tag=friend_all] +include::{example}/5/main.cpp[tag=payroll] ---- -Be aware, though, that the overriders of _any_ method called `poke` - with any -signature - are granted friendship. - -We can also befriend individual overriders: +We can now implement the `pay` overriders: [source,c++] ---- -include::example$friendship.cpp[tag=friend] +include::{example}/5/main.cpp[tag=overriders] ---- + +We can also declare friendship _en_ _masse_: + +[source,c++] +---- +include::{example}/6/main.cpp[tag=payroll] +---- + +Note, however, that this makes all the overriders of _any_ `pay` method, with +any signature, in the current namespace, friends of `Payroll`. Unfortunately, +C++ does not currently allow partial specialization of friend declarations. diff --git a/doc/modules/ROOT/pages/headers.adoc b/doc/modules/ROOT/pages/headers.adoc index 19d3c79..2e1f90f 100644 --- a/doc/modules/ROOT/pages/headers.adoc +++ b/doc/modules/ROOT/pages/headers.adoc @@ -1,111 +1,102 @@ -[#headers] -= xref:headers.adoc[Headers] +:example: ../examples/rolex -{empty} +## Headers -## Headers for General Use +Typically, `BOOST_OPENMETHOD` go in headers, while +`BOOST_OPENMETHOD_CLASSES` and `BOOST_OPENMETHOD_OVERRIDE` go in +implementation files. -The following headers are sufficient for most basic uses of the library. +Let's use a payroll application as an example. +We have two roles: `employee` and `salesman`, and a `pay` method that +computes the monthly pay of an employee. +We want to override and call `pay` from from multiple translation units, so +we put it in a header: -* xref:#main[`boost/openmethod.hpp`] to define open-methods and overriders using -convenient macros. +[source,c++] +---- +include::{example}/1/roles.hpp[tag=content] +---- -* xref:#initialize[`boost/openmethod/initialize.hpp`] to initialize the library. -Typically only included in the translation unit containing `main`. +`BOOST_OPENMETHOD` _defines_ an inline function, so it can be called only +once in a translation unit. +The include guards see to this. -The following headers make it possible to use standard smart pointers in virtual -parameters: +Let's write the override for "just" employees - they get a fixed salary. +`BOOST_OPENMETHOD_OVERRIDE` _adds_ an overrider to the method. +We don't want to add the same multiple times; that would create an +ambiguity. +Thus it should go in an implementation file: -* xref:#std_shared_ptr[`boost/openmethod/interop/std_shared_ptr.hpp`] to use -`std::shared_ptr` in virtual parameters. +[source,c++] +---- +include::{example}/1/employee.cpp[tag=content] +---- -* xref:#std_unique_ptr[`boost/openmethod/interop/std_unique_ptr.hpp`] to use -`std::unique_ptr` in virtual parameters. +Salesmen get a salary like employees, and on top get a commission on their +sales. The `next` function, available only in the body of an overrider, calls +the next most specialized overrider. It is similar to `super` in other +languages, except that it does not stand for an object, but for an overrider. -*The remaining headers are for advanced use*. +[source,c++] +---- +include::{example}/1/salesman.cpp[tag=content] +---- -## Pre-Core Headers +Paytime! -The following headers can be included before `core.hpp` to define custom -registries and policies, and override the default registry by defining -xref:BOOST_OPENMETHOD_DEFAULT_REGISTRY.adoc[`BOOST_OPENMETHOD_DEFAULT_REGISTRY`]. +[source,c++] +---- +include::{example}/1/main.cpp[tag=content] +---- -### boost/openmethod/preamble.hpp +In the previous example, we used `next` to call the super-overrider. We can also +call an overrider directly. To do this, we can declare the overrider in the +header, and define it in an implementation file: -Defines `registry` and stock policy categories. Also defines all types and -functions necessary for the definition of `registry`. +[source,c++] +---- +include::{example}/2/roles.hpp[tag=content] +---- -### boost/openmethod/policies/std_rtti.hpp +Unlike function declarations, +xref:BOOST_OPENMETHOD_DECLARE_OVERRIDER.adoc[BOOST_OPENMETHOD_DECLARE_OVERRIDER] +cannot appear multiple times in a translation unit with the same arguments. +Also, it requires the _method_ itself to be defined prior using this macro. -Provides an implementation of the `rtti` policy using standard RTTI. +Overriders are placed in _overrider_ _containers_. An overrider container is a +class template named after the method, declared in the current namespace. It is +specialized for each overrider signature. Macro +xref:BOOST_OPENMETHOD_OVERRIDER.adoc[BOOST_OPENMETHOD_OVERRIDER] takes the same +arguments `BOOST_OPENMETHOD_OVERRIDE`, and expands to the corresponding +specialization of the overrider container. Containers have a static member +function `fn` that contains the body the overrider, provided by the user. We can +call the overrider for `Employee` like so: -### boost/openmethod/policies/fast_perfect_hash.hpp +[source,c++] +---- +include::{example}/2/salesman.cpp[tag=content] +---- -Provides an implementation of the `hash` policy using a fast perfect hash -function. +This is similar to a virtual function calling a base overrider. Virtual +functions don't have the equivalent of Smalltalk's or Python's `super`, but +OpenMethod does, it's `next`. It is almost always the right choice. -### boost/openmethod/policies/vptr_vector.hpp +The exception is: when performance is critical, we may want to inline the call +to the base overrider. +xref:BOOST_OPENMETHOD_INLINE_OVERRIDE.adoc[BOOST_OPENMETHOD_INLINE_OVERRIDE] +defines the overrider as an inline function, and it can go in a header file: -Provides an implementation of the `vptr` policy that stores the v-table pointers -in a `std::vector` indexed by type ids, possibly hashed. +[source,c++] +---- +include::{example}/3/roles.hpp[tag=content] +---- -### boost/openmethod/policies/default_error_handler.hpp +With inlining the overrider for `Salesman` compiles to (clang-20, x64-linux): -Provides an implementation of the `error_handler` policy that calls a -`std::function` when an error is encountered, and before -the library aborts the program. - -### boost/openmethod/policies/stderr_output.hpp - -Provides an implementation of the `output` policy that writes diagnostics to -the C standard error stream (not using iostreams). - -### boost/openmethod/default_registry.hpp - -Defines the default registry, which contains all the stock policies listed -above. Includes all the headers listed in the preamble section so far. - -### boost/openmethod/policies/static_rtti.hpp - -Provides a minimal implementation of the `rtti` policy that does not depend on -standard RTTI. - -### boost/openmethod/policies/throw_error_handler.hpp - -Provides an implementation of the `error_handler` policy that throws errors as -exceptions. - -### boost/openmethod/policies/vptr_map.hpp - -Provides an implementation of the `vptr` policy that stores the v-table pointers -in a map (by default a `std::map`) indexed by type ids. - -## High-level Headers - -### boost/openmethod/core.hpp - -Defines the main constructs of the library: methods, overriders and virtual -pointers, and mechanisms to implement them. Does not define any public macros -apart from `BOOST_OPENMETHOD_DEFAULT_REGISTRY`, if it is not defined already. - -### boost/openmethod/macros.hpp - -Defines the public macros of the library, such as `BOOST_OPENMETHOD`, -`BOOST_OPENMETHOD_CLASSES`, etc. - -There is little point in including this header directly, as this has the same -effect as including `boost/openmethod.hpp`, which is shorter. - -### boost/openmethod.hpp - -Includes `core.hpp` and `macros.hpp`. - -### boost/openmethod/interop/std_shared_ptr.hpp - -Provides a `virtual_traits` specialization that make it possible to use a -`std::shared_ptr` in place of a raw pointer or reference in virtual parameters. - -### boost/openmethod/interop/std_unique_ptr.hpp - -Provides a `virtual_traits` specialization that make it possible to use a -`std::unique_ptr` in place of a raw pointer or reference in virtual parameters. +[source,asm] +---- +movsd xmm0, qword ptr [rsi + 8] +mulsd xmm0, qword ptr [rip + .LCPI1_0] +addsd xmm0, qword ptr [rip + .LCPI1_1] +ret +---- diff --git a/doc/modules/ROOT/pages/headers_namespaces.adoc b/doc/modules/ROOT/pages/headers_namespaces.adoc deleted file mode 100644 index e7f5c36..0000000 --- a/doc/modules/ROOT/pages/headers_namespaces.adoc +++ /dev/null @@ -1,193 +0,0 @@ - -## Headers and Namespaces - -Most real-life programs will be organized in multiple files and multiple -namespaces. OpenMethod interacts with headers and namespaces naturally, if -using-directives are avoided. In that case, there are a few things to be aware -of. - -Let's break the Animals example into headers and namespaces. First we put -`Animal` in its own header and namespace: - -[source,c++] ----- -include::example$headers_namespaces/animal.hpp[] ----- - -`BOOST_OPENMETHOD` can be placed in a header file. It adds several constructs to -the current namespace: - -* It declares (but does not define) a `struct` named after the method. - -* It declares (but does not define) a _guide_ function. It is also named after -the method, and it has the same signature (with the `virtual_` decorators -stripped). It is used to match methods and overriders. It is never defined and -it is "called" only in a non-evaluated context. - -* It defines an inline function with the same name and signature as the -method (with the `virtual_` decorators stripped). - -Next, let's implement the `Cat` class, and a derived class, `Cheetah`, in the -`felines` namespace: - -[source,c++] ----- -include::example$headers_namespaces/cat.hpp[] ----- - -[source,c++] ----- -include::example$headers_namespaces/cat.cpp[] ----- - -`BOOST_OPENMETHOD_CLASSES` should be placed in an implementation file. It can -also go in a header file, but this wastes space, as the same registrar will be -created in every translation unit that includes the header. It doesn't matter -which namespace the macro is called in. It can be used with any class name in -scope, or with qualified names. - -`BOOST_OPENMETHOD_OVERRIDE` uses the guide function declared by -`BOOST_OPENMETHOD` to locate a method that can be called with the same arguments -as the overrider itself. It "calls" the guide function in a non-evaluated -context, passing it a `std::ostream&` and a `virtual_ptr`. The return type -of the guide function is the method to add the overrider to. Exactly one guide -function must match. The normal rules of overload resolution apply. In that -case, the guide function is found via argument dependant lookup (ADL). - -The macro adds several constructs to the current namespace: - -* It declares (but does not define) a `struct` template with one type parameter, -named after the method. The template acts like a container for overriders. - -* It specializes the template for the signature of the overrider. Inside the -struct, it defines the `next` and `has_next` members, and a static function -called `fn`. The block following the macro is the body of the `fn` function. - -It follows that `BOOST_OPENMETHOD_OVERRIDE` should be placed in an -implementation file. `BOOST_OPENMETHOD_INLINE_OVERRIDE` works like -`BOOST_OPENMETHOD_OVERRIDE`, but it defines the `fn` function as inline, so it -can be used in a header file. - -The overrider for Cats can be accessed in the same translation unit, after it -has been defined, using the `BOOST_OPENMETHOD_OVERRIDER` macro. It expands to -the specialization of the overrider container for the overrider's signature. We -call the static `fn` function to call the overrider. - -NOTE: The Cheetah overrider calls the specific overrider for `Cat`, for -illustration purpose. It is usually better to call `next` instead. - -Let's implement the `Dog` class, in the `canines` namespace. This time we want -the overrider to be accessible in other translation units. We can declare an -overrider with `BOOST_OPENMETHOD_DECLARE_OVERRIDER`, without actually defining -the static function `fn` just yet. - -[source,c++] ----- -include::example$headers_namespaces/dog.hpp[] ----- - -Unlike function declarations, which can occur multiple times in a TU, an -overrider declaration cannot. For example, this is illegal: - -```c++ -BOOST_OPENMETHOD_DECLARE_OVERRIDER( - poke, (std::ostream&, virtual_ptr), void); - -BOOST_OPENMETHOD_DECLARE_OVERRIDER( - poke, (std::ostream&, virtual_ptr), void); -``` - -Now we use `BOOST_OPENMETHOD_DEFINE_OVERRIDER` to define the overrider: - -[source,c++] ----- -include::example$headers_namespaces/dog.cpp[] ----- - -Let's look at the main program now. It derives `Bulldog` from `Dog` and provides -an overrider for the new class: - -[source,c++] ----- -include::example$headers_namespaces/main.cpp[] ----- - -Again ADL plays a role: it helps the overrider (and `main`) to locate the `poke` -method. - -This example is the "happy scenario", where namespaces are used conservatively. - -The `OVERRIDE` macros don't interact well with `using` directives. For example -this code: - -```c++ -using namespace animals; -using namespace canines; -using namespace felines; - -struct Bulldog : Dog { - using Dog::Dog; -}; - -BOOST_OPENMETHOD_CLASSES(Dog, Bulldog); - -BOOST_OPENMETHOD_OVERRIDE( - poke, (std::ostream & os, virtual_ptr dog), void) { - next(os, dog); - os << " and bites back"; -} -``` - -...will fail to compile, with an error like "reference to -'poke_boost_openmethod_overriders' is ambiguous". That is because the overrider -containers exist in both the canines and felines namespaces, with the same name. - -Finally, the names passed as first arguments to the BOOST_OPENMETHOD and -BOOST_OPENMETHOD_OVERRIDE macros must be identifiers. Qualified names are not -allowed. Consider: - -```c++ -using animals::Animal; - -namespace app_specific_behavior { - -BOOST_OPENMETHOD( - meet, (std::ostream&, virtual_ptr, virtual_ptr), void); - -} // namespace app_specific_behavior - -BOOST_OPENMETHOD_OVERRIDE( - meet, (std::ostream& os, virtual_ptr, virtual_ptr), void) { - os << "ignore"; -} -``` - -Here, the guide function cannot be found, even via ADL. We get an error like -"use of undeclared identifier 'meet_boost_openmethod_guide'". How do we solve -this? We might be tempted to use a qualified name: -`app_specific_behavior::meet`: - -```c++ -BOOST_OPENMETHOD_OVERRIDE( - app_specific_behavior::meet, - (std::ostream& os, virtual_ptr, virtual_ptr), void) { - os << "ignore"; -} -``` - -But `BOOST_OPENMETHOD_OVERRIDE` also uses the name to derive the overrider -container's name, using preprocessor token pasting, resulting in an invalid -declaration error. - -We need to do is to make `BOOST_OPENMETHOD_OVERRIDE` "see" the guide function. -Its name is returned by macro `BOOST_OPENMETHOD_GUIDE(NAME)`. We can use a -using-declaration to bring the guide function into the current scope: - -```c++ -using app_specific_behavior::BOOST_OPENMETHOD_GUIDE(meet); - -BOOST_OPENMETHOD_OVERRIDE( - meet, (std::ostream& os, virtual_ptr, virtual_ptr), void) { - os << "ignore"; -} -``` diff --git a/doc/modules/ROOT/pages/introduction.adoc b/doc/modules/ROOT/pages/introduction.adoc index 03b92a2..0a44588 100644 --- a/doc/modules/ROOT/pages/introduction.adoc +++ b/doc/modules/ROOT/pages/introduction.adoc @@ -64,7 +64,7 @@ Otherwise, there are serious problems with this approach. * Programs that use the Node classes get the overriders of `postfix`, even those that don't call `postfix` anywhere. -* They also pull in `postfix`'s transitive dependencies, in this case +* They also pull in `postfix`{empty}'s transitive dependencies, in this case `std::ostream`, locales, facets, exceptions, etc. Note that now we need to include `` _before_ defining the classes. diff --git a/doc/modules/ROOT/pages/multiple_dispatch.adoc b/doc/modules/ROOT/pages/multiple_dispatch.adoc index e5e8bc0..8070429 100644 --- a/doc/modules/ROOT/pages/multiple_dispatch.adoc +++ b/doc/modules/ROOT/pages/multiple_dispatch.adoc @@ -1,20 +1,30 @@ +:example: ../examples/rolex ## Multiple Dispatch -A method can have more than one `virtual_ptr` parameter. For example: +A method can have more than one virtual parameter. This is often called +"multi-methods" or "multiple dispatch". All the virtual parameters participate +equally in overrider selection, following the same rules as those governing +overload resolution - except that the selection happens at runtime, and takes +into account the argument's dynamic types. + +Multiple dispatch is occasionally useful, and, when it is needed, it can be +difficult to implement correctly and efficiently by hand. For example, given the following classes: [source,c++] ---- -include::example$hello_world.cpp[tag=multi] +include::{example}/7/main.cpp[tag=classes] ---- +We want to implement an `approve` method that determines who can make what kind +of expenses. Employees can take any public transportation; managers can also +take a taxi, for a ride cost up to $100.00; and founders can take any +transportation, including a private jet. This can be expressed like so: + [source,c++] ---- -include::example$hello_world.cpp[tag=multi_call,indent=0] +include::{example}/7/main.cpp[tag=approve] ---- -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. +Note that `approve` takes advantage of inheritance to avoid listing all possible +cases explicitly. This is important, because the number of cases grows diff --git a/doc/modules/ROOT/pages/namespaces.adoc b/doc/modules/ROOT/pages/namespaces.adoc new file mode 100644 index 0000000..c6efbfe --- /dev/null +++ b/doc/modules/ROOT/pages/namespaces.adoc @@ -0,0 +1,49 @@ +:example: ../examples/rolex + +## Namespaces + +Note;; This section uses overrider containers, described in the +xref:headers.adoc[Headers] section. + +xref:BOOST_OPENMETHOD.adoc[BOOST_OPENMETHOD] defines a method in the current +namespace. xref:BOOST_OPENMETHOD_OVERRIDE.adoc[BOOST_OPENMETHOD_OVERRIDE] works +_across_ namespaces. Overriders are not required to be in the same namespace as +the method they override. The macro adds the overrider a method that can be +called with the same arguments, possibly located via argument dependant lookup. + +Overrider containers are added to the current namespace. It follows that the +same method can have overriders in several different container, in different +namespaces. This must be taken into account when calling an overrider +explicitly. Let's put Employee and Salesman in their own namespaces: + +[source,c++] +---- +include::{example}/4/roles.hpp[tag=content] +---- + +When we try to compile `salesman.cpp`: + +[source,c++] +---- +include::{example}/3/salesman.cpp[tag=content] +---- + +We get an error like: + +``` +error: implicit instantiation of undefined template +'sales::pay_boost_openmethod_overriders< + double (boost::openmethod::virtual_ptr)> +``` + +This is because the overrider container for `pay` in namespace `sales` is +specialized for `Salesman`, but not for `Employee`. The overrider for Employee +is in a specialization of a different container, in namespace `employees`. + +The solution is to qualify the overrider container with the `employees` +namespace: + +[source,c++] +---- +include::{example}/3/salesman.cpp[tag=content] +---- diff --git a/doc/modules/ROOT/pages/performance.adoc b/doc/modules/ROOT/pages/performance.adoc index 31d1a85..1fb6ad5 100644 --- a/doc/modules/ROOT/pages/performance.adoc +++ b/doc/modules/ROOT/pages/performance.adoc @@ -5,7 +5,7 @@ Open-methods can be as fast as ordinary virtual member functions when compiled with optimization. First, let's examine the code generated by clang for an ordinary virtual -function call. +function call: [source,c++] ---- @@ -38,19 +38,19 @@ This compiles to (variable names are shortened for readability): [source,asm] ---- - mov rax, rdi - mov rcx, qword ptr [rdi] - mov rdi, qword ptr [rip + mult] - imul rdi, qword ptr [rcx - 8] - movzx ecx, byte ptr [rip + shift] - shr rdi, cl - mov rdx, rsi - mov rcx, qword ptr [rip + vptr_vector_vptrs] - mov rdi, qword ptr [rcx + 8*rdi] - mov rcx, qword ptr [rip + fn+88] - mov rcx, qword ptr [rdi + 8*rcx] - mov rsi, rax - jmp rcx # TAILCALL + mov rax, rdi + mov rcx, qword ptr [rdi] + mov rdi, qword ptr [rip + mult] + imul rdi, qword ptr [rcx - 8] + movzx ecx, byte ptr [rip + shift] + shr rdi, cl + mov rdx, rsi + mov rcx, qword ptr [rip + vptr_vector_vptrs] + mov rdi, qword ptr [rcx + 8*rdi] + mov rcx, qword ptr [rip + fn+88] + mov rcx, qword ptr [rdi + 8*rcx] + mov rsi, rax + jmp rcx # TAILCALL ---- This is quite a few instructions more. Upon closer examination, we see that many @@ -62,9 +62,9 @@ llvm-mca estimates a throughput of 4 cycles per dispatch. 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 and RDTSC-based benchmarks suggest that dispatching an open-methods with a -single virtual argument _via_ _a_ _reference_ is between 30% and 50% slower than -calling the equivalent virtual function, with an empty body and no other +Micro- and RDTSC-based benchmarks suggest that dispatching an open-methods with +a single virtual argument _via_ _a_ _reference_ is between 30% and 50% slower +than calling the equivalent virtual function, with an empty body and no other arguments. In most real programs, the overhead would be unnoticeable. *However*, `call_via_ref` does two things: it constructs a `virtual_ptr` @@ -94,10 +94,10 @@ mov rax, qword ptr [rdi + 8*rax] jmp rax # TAILCALL ---- -`virtual_ptr` arguments as passed through the method call, to the overrider, +`virtual_ptr` arguments are passed through the method call, to the overrider, which can use them to make further method calls. -A program designed with open-methods in mind should store `virtual_ptr`{empty}'s +A program designed with open-methods in mind should use `virtual_ptr`{empty}'s in place of plain pointers or references, as much as possible. Here is the Node example, rewritten to use `virtual_ptr`{empty}'s thoughout: diff --git a/doc/modules/ROOT/pages/ref_headers.adoc b/doc/modules/ROOT/pages/ref_headers.adoc new file mode 100644 index 0000000..2dd0932 --- /dev/null +++ b/doc/modules/ROOT/pages/ref_headers.adoc @@ -0,0 +1,111 @@ +[#ref_headers] += xref:ref_headers.adoc[Headers] + +{empty} + +## Headers for General Use + +The following headers are sufficient for most basic uses of the library. + +* xref:#main[`boost/openmethod.hpp`] to define open-methods and overriders using +convenient macros. + +* xref:#initialize[`boost/openmethod/initialize.hpp`] to initialize the library. +Typically only included in the translation unit containing `main`. + +The following headers make it possible to use standard smart pointers in virtual +parameters: + +* xref:#std_shared_ptr[`boost/openmethod/interop/std_shared_ptr.hpp`] to use +`std::shared_ptr` in virtual parameters. + +* xref:#std_unique_ptr[`boost/openmethod/interop/std_unique_ptr.hpp`] to use +`std::unique_ptr` in virtual parameters. + +*The remaining headers are for advanced use*. + +## Pre-Core Headers + +The following headers can be included before `core.hpp` to define custom +registries and policies, and override the default registry by defining +xref:BOOST_OPENMETHOD_DEFAULT_REGISTRY.adoc[`BOOST_OPENMETHOD_DEFAULT_REGISTRY`]. + +### boost/openmethod/preamble.hpp + +Defines `registry` and stock policy categories. Also defines all types and +functions necessary for the definition of `registry`. + +### boost/openmethod/policies/std_rtti.hpp + +Provides an implementation of the `rtti` policy using standard RTTI. + +### boost/openmethod/policies/fast_perfect_hash.hpp + +Provides an implementation of the `hash` policy using a fast perfect hash +function. + +### boost/openmethod/policies/vptr_vector.hpp + +Provides an implementation of the `vptr` policy that stores the v-table pointers +in a `std::vector` indexed by type ids, possibly hashed. + +### boost/openmethod/policies/default_error_handler.hpp + +Provides an implementation of the `error_handler` policy that calls a +`std::function` when an error is encountered, and before +the library aborts the program. + +### boost/openmethod/policies/stderr_output.hpp + +Provides an implementation of the `output` policy that writes diagnostics to +the C standard error stream (not using iostreams). + +### boost/openmethod/default_registry.hpp + +Defines the default registry, which contains all the stock policies listed +above. Includes all the headers listed in the preamble section so far. + +### boost/openmethod/policies/static_rtti.hpp + +Provides a minimal implementation of the `rtti` policy that does not depend on +standard RTTI. + +### boost/openmethod/policies/throw_error_handler.hpp + +Provides an implementation of the `error_handler` policy that throws errors as +exceptions. + +### boost/openmethod/policies/vptr_map.hpp + +Provides an implementation of the `vptr` policy that stores the v-table pointers +in a map (by default a `std::map`) indexed by type ids. + +## High-level Headers + +### boost/openmethod/core.hpp + +Defines the main constructs of the library: methods, overriders and virtual +pointers, and mechanisms to implement them. Does not define any public macros +apart from `BOOST_OPENMETHOD_DEFAULT_REGISTRY`, if it is not defined already. + +### boost/openmethod/macros.hpp + +Defines the public macros of the library, such as `BOOST_OPENMETHOD`, +`BOOST_OPENMETHOD_CLASSES`, etc. + +There is little point in including this header directly, as this has the same +effect as including `boost/openmethod.hpp`, which is shorter. + +### boost/openmethod.hpp + +Includes `core.hpp` and `macros.hpp`. + +### boost/openmethod/interop/std_shared_ptr.hpp + +Provides a `virtual_traits` specialization that make it possible to use a +`std::shared_ptr` in place of a raw pointer or reference in virtual parameters. + +### boost/openmethod/interop/std_unique_ptr.hpp + +Provides a `virtual_traits` specialization that make it possible to use a +`std::unique_ptr` in place of a raw pointer or reference in virtual parameters. diff --git a/doc/modules/ROOT/pages/macros.adoc b/doc/modules/ROOT/pages/ref_macros.adoc similarity index 100% rename from doc/modules/ROOT/pages/macros.adoc rename to doc/modules/ROOT/pages/ref_macros.adoc diff --git a/doc/modules/ROOT/pages/smart_pointers.adoc b/doc/modules/ROOT/pages/smart_pointers.adoc index 8976afc..c37ed53 100644 --- a/doc/modules/ROOT/pages/smart_pointers.adoc +++ b/doc/modules/ROOT/pages/smart_pointers.adoc @@ -22,15 +22,35 @@ counterparts - e.g. from `virtual_ptr>` to `virtual_ptr`. Methods and overriders typically use plain `virtual_ptr`{empty}'s, although it is not always the case. For example, consider a `transpose` method for matrices. If the matrix is symmetric, the -overrider should return its argument. This can be implemented by passing a `virtual_ptr>` to the method. +overrider should return its argument. This can be implemented by passing a +`virtual_ptr>` to the method. -The reverse conversion, from plain to smart, does not exist, because it would be +The reverse conversion, from plain to smart, does not exist, because it is unsafe. A smart `virtual_ptr` can be constructed from a corresponding smart pointer, but not directly from a plain reference or pointer, because it has the potential to accidentally create smart pointers. +The library provides aliases for standard smart pointers: + +- `unique_virtual_ptr` is an alias for `virtual_ptr>` + +- `shared_virtual_ptr` is an alias for `virtual_ptr>` + +The standard library provides `std::make_unique` and `std::make_shared` to +create smart pointers. They are convenient, robust, and, in the case of +`std::shared_ptr`, more efficient. OpenMethod provides counterparts: + +- `make_unique_virtual_ptr(...)` + +- `make_shared_virtual_ptr(...)` + +Since these functions create the object, they know its exact type with +certainty. Thus they don't need to perform a hash table lookup to find the +appropriate v-table; they simply read it from a static variable. As a +consequence, they don't even require `Class` to be polymorphic. + Here is a variation of the AST example that uses dynamic allocation and unique pointers: diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index 2c6d207..fff1a68 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -1813,7 +1813,8 @@ auto operator!=( template struct virtual_traits, Registry> { //! `Class`, stripped from cv-qualifiers. - using virtual_type = typename virtual_ptr::element_type; + using virtual_type = + std::remove_cv_t::element_type>; //! Return a reference to a non-modifiable `Class` object. //! @param arg A reference to a non-modifiable `Class` object. @@ -1858,7 +1859,8 @@ struct virtual_traits, Registry> { template struct virtual_traits&, Registry> { //! `Class`, stripped from cv-qualifiers. - using virtual_type = typename virtual_ptr::element_type; + using virtual_type = + std::remove_cv_t::element_type>; //! Return a reference to a non-modifiable `Class` object. //! @param arg A reference to a non-modifiable `Class` object. @@ -2317,8 +2319,7 @@ class method (void)&impl; } - static override_impl - impl; + static override_impl impl; }; }; @@ -2628,21 +2629,45 @@ template struct validate_overrider_parameter, virtual_ptr, void> : std::true_type {}; -template +template struct validate_overrider_parameter< - virtual_ptr, virtual_ptr, void> : std::is_same { - static_assert(validate_overrider_parameter::value, "registry mismatch"); + virtual_ptr, virtual_ptr, void> : std::true_type { + static_assert(std::is_same_v, "registry mismatch"); + using C1 = virtual_type, R>; + using C2 = virtual_type, R>; + static_assert( + std::is_base_of_v && + std::is_convertible_v, virtual_ptr>, + "method parameter must be an unambiguous accessible base " + "of corresponding overrider parameter"); }; -template +template struct validate_overrider_parameter< - const virtual_ptr&, const virtual_ptr&, void> - : validate_overrider_parameter, virtual_ptr> {}; + const virtual_ptr&, const virtual_ptr&, void> + : std::true_type { + static_assert(std::is_same_v, "registry mismatch"); + using C1 = virtual_type&, R>; + using C2 = virtual_type&, R>; + static_assert( + std::is_base_of_v && + std::is_convertible_v, virtual_ptr>, + "method parameter must be an unambiguous accessible base " + "of corresponding overrider parameter"); +}; -template +template struct validate_overrider_parameter< - virtual_ptr&&, virtual_ptr&&, void> - : validate_overrider_parameter, virtual_ptr> {}; + virtual_ptr&&, virtual_ptr&&, void> : std::true_type { + static_assert(std::is_same_v, "registry mismatch"); + using C1 = virtual_type&&, R>; + using C2 = virtual_type&&, R>; + static_assert( + std::is_base_of_v && + std::is_convertible_v, virtual_ptr>, + "method parameter must be an unambiguous accessible base " + "of corresponding overrider parameter"); +}; } // namespace detail diff --git a/include/boost/openmethod/macros.hpp b/include/boost/openmethod/macros.hpp index e85ad96..4ef7973 100644 --- a/include/boost/openmethod/macros.hpp +++ b/include/boost/openmethod/macros.hpp @@ -62,6 +62,8 @@ struct va_args { ::boost::openmethod::detail::va_args<__VA_ARGS__>::registry> #define BOOST_OPENMETHOD(NAME, ARGS, ...) \ + template \ + struct BOOST_OPENMETHOD_OVERRIDERS(NAME); \ struct BOOST_OPENMETHOD_ID(NAME); \ template \ typename ::boost::openmethod::detail::enable_forwarder< \ @@ -70,8 +72,8 @@ struct va_args { ForwarderParameters...>::type \ BOOST_OPENMETHOD_GUIDE(NAME)(ForwarderParameters && ... args); \ template \ - inline auto NAME(ForwarderParameters&&... args) \ - ->typename ::boost::openmethod::detail::enable_forwarder< \ + inline auto NAME(ForwarderParameters&&... args) -> \ + typename ::boost::openmethod::detail::enable_forwarder< \ void, BOOST_OPENMETHOD_TYPE(NAME, ARGS, __VA_ARGS__), \ ::boost::openmethod::detail::va_args<__VA_ARGS__>::return_type, \ ForwarderParameters...>::type { \ @@ -89,27 +91,24 @@ struct va_args { }; #define BOOST_OPENMETHOD_DECLARE_OVERRIDER(NAME, ARGS, ...) \ - template \ + template \ struct BOOST_OPENMETHOD_OVERRIDERS(NAME); \ template<> \ struct BOOST_OPENMETHOD_OVERRIDERS(NAME)<__VA_ARGS__ ARGS> { \ BOOST_OPENMETHOD_DETAIL_LOCATE_METHOD(NAME, ARGS); \ - static auto fn ARGS -> __VA_ARGS__; \ + static auto fn ARGS->__VA_ARGS__; \ static auto has_next() -> bool; \ template \ static auto next(Args&&... args) -> decltype(auto); \ }; \ inline auto BOOST_OPENMETHOD_OVERRIDERS( \ - NAME)<__VA_ARGS__ ARGS>::has_next() \ - ->bool { \ + NAME)<__VA_ARGS__ ARGS>::has_next() -> bool { \ return boost_openmethod_detail_locate_method_aux< \ void ARGS>::type::has_next(); \ } \ template \ inline auto BOOST_OPENMETHOD_OVERRIDERS(NAME)<__VA_ARGS__ ARGS>::next( \ - Args&&... args) \ - ->decltype(auto) { \ - BOOST_ASSERT(has_next()); \ + Args&&... args) -> decltype(auto) { \ return boost_openmethod_detail_locate_method_aux< \ void ARGS>::type::next(std::forward(args)...); \ } @@ -124,7 +123,7 @@ struct va_args { #define BOOST_OPENMETHOD_DEFINE_OVERRIDER(NAME, ARGS, ...) \ BOOST_OPENMETHOD_DETAIL_REGISTER_OVERRIDER(NAME, ARGS, __VA_ARGS__) \ auto BOOST_OPENMETHOD_OVERRIDER(NAME, ARGS, __VA_ARGS__)::fn ARGS \ - ->boost::mp11::mp_back> + -> boost::mp11::mp_back> #define BOOST_OPENMETHOD_OVERRIDE(NAME, ARGS, ...) \ BOOST_OPENMETHOD_DECLARE_OVERRIDER(NAME, ARGS, __VA_ARGS__) \ @@ -134,7 +133,7 @@ struct va_args { BOOST_OPENMETHOD_DECLARE_OVERRIDER(NAME, ARGS, __VA_ARGS__) \ BOOST_OPENMETHOD_DETAIL_REGISTER_OVERRIDER(NAME, ARGS, __VA_ARGS__) \ inline auto BOOST_OPENMETHOD_OVERRIDER(NAME, ARGS, __VA_ARGS__)::fn ARGS \ - ->boost::mp11::mp_back> + -> boost::mp11::mp_back> #define BOOST_OPENMETHOD_CLASSES(...) \ BOOST_OPENMETHOD_REGISTER(::boost::openmethod::use_classes<__VA_ARGS__>);