diff --git a/include/yorel/yomm2/core.hpp b/include/yorel/yomm2/core.hpp index 0171076b..bf46f6f5 100644 --- a/include/yorel/yomm2/core.hpp +++ b/include/yorel/yomm2/core.hpp @@ -414,7 +414,9 @@ struct method : Policy::method_info_type { detail::ti_ptr tis[sizeof...(args)]; error.tis = tis; auto ti_iter = tis; - (..., (*ti_iter++ = detail::get_tip(args))); + (..., + (*ti_iter++ = + detail::get_tip(detail::virtual_traits::ref(args)))); detail::error_handler(error_type(std::move(error))); abort(); // in case user handler "forgets" to abort } @@ -427,7 +429,9 @@ struct method : Policy::method_info_type { detail::ti_ptr tis[sizeof...(args)]; error.tis = tis; auto ti_iter = tis; - (..., (*ti_iter++ = detail::get_tip(args))); + (..., + (*ti_iter++ = + detail::get_tip(detail::virtual_traits::ref(args)))); detail::error_handler(error_type(std::move(error))); abort(); // in case user handler "forgets" to abort } @@ -600,63 +604,54 @@ class virtual_ptr_aux { virtual_ptr_aux(Class& obj, mptr_type mptr) : obj(obj), mptr(mptr) { } - template - static auto final(OtherClass& obj) { + template + static auto final(Other& obj) { using namespace detail; mptr_type mptr; if constexpr (Policy::use_indirect_method_pointers) { mptr = detail::indirect_method_table< - typename detail::virtual_traits::polymorphic_type, + typename detail::virtual_traits::polymorphic_type, Policy>; } else { mptr = detail::method_table< - typename detail::virtual_traits::polymorphic_type, + typename detail::virtual_traits::polymorphic_type, Policy>; } if constexpr (debug) { // check that dynamic type == static type - auto key = virtual_traits::key(obj); + auto key = virtual_traits::key(obj); auto final_key = - &typeid(typename virtual_traits::polymorphic_type); + &typeid(typename virtual_traits::polymorphic_type); if (key != final_key) { error_handler(method_table_error{key}); } - - // check that OtherClass is registered - if constexpr (Policy::use_indirect_method_pointers) { - check_method_pointer(*mptr, key); - } else { - check_method_pointer(mptr, key); - } } return virtual_ptr(obj, mptr); } - template - static auto dynamic_method_table(OtherClass& obj) { + template + static auto dynamic_method_table(Other& obj) { using namespace detail; mptr_type mptr; - auto key = virtual_traits::key(obj); + auto key = virtual_traits::key(obj); auto final_key = - &typeid(typename virtual_traits::polymorphic_type); + &typeid(typename virtual_traits::polymorphic_type); if (key == final_key) { if constexpr (Policy::use_indirect_method_pointers) { mptr = detail::indirect_method_table< - typename detail::virtual_traits< - OtherClass&>::polymorphic_type, + typename detail::virtual_traits::polymorphic_type, Policy>; } else { mptr = detail::method_table< - typename detail::virtual_traits< - OtherClass&>::polymorphic_type, + typename detail::virtual_traits::polymorphic_type, Policy>; } } else { @@ -704,22 +699,21 @@ class virtual_ptr public: // using virtual_ptr_aux::virtual_ptr_aux; - template - virtual_ptr(OtherClass& obj) : base(obj, this->dynamic_method_table(obj)) { + template + virtual_ptr(Other& obj) : base(obj, this->dynamic_method_table(obj)) { } - template - virtual_ptr(OtherClass&& obj, typename base::mptr_type mptr) - : base(obj, mptr) { + template + virtual_ptr(Other&& obj, typename base::mptr_type mptr) : base(obj, mptr) { } - template - virtual_ptr(virtual_ptr& other) + template + virtual_ptr(virtual_ptr& other) : base(other.obj, other.mptr) { } - template - virtual_ptr(const virtual_ptr& other) + template + virtual_ptr(const virtual_ptr& other) : base(other.obj, other.mptr) { } diff --git a/include/yorel/yomm2/detail.hpp b/include/yorel/yomm2/detail.hpp index 109fbeba..eb53dc5a 100644 --- a/include/yorel/yomm2/detail.hpp +++ b/include/yorel/yomm2/detail.hpp @@ -339,12 +339,12 @@ template struct virtual_traits> { using polymorphic_type = Class; - static auto ref(virtual_ptr ptr) { - return ptr; + static Class& ref(virtual_ptr ptr) { + return *ptr.get(); } - static auto key(virtual_ptr arg) { - return &typeid(*arg); + static auto key(const Class& arg) { + return &typeid(arg); } template diff --git a/reference.in/README.md b/reference.in/README.md index d7916b95..667809bf 100644 --- a/reference.in/README.md +++ b/reference.in/README.md @@ -12,7 +12,7 @@ Yuriy Solodkyy, and Bjarne Stroustrup. This implementation diverges from the paper on the following points: * A "base-method" is called a "method declaration" in YOMM2, and an "overrider" - is called a "method definition". This is because + is called a "method definition". This is because * YOMM2 has a mechanism (`next`) to call the next most specialised method. * The paper allows only references for virtual parameters. YOMM2 also allows pointers and smart pointers. @@ -153,12 +153,10 @@ This was the recommended header before version 1.3.0. Includes | ->define_method | macro | add a definition to a method | | ->define_method_inline | macro | add an definition to a method in a container, and make it inline | | ->derived | class template | helper for intrusive modes | -| ->direct | type | tag for direct intrusive mode | | ->error_handler_type | type | handler function | | ->error_type | variant | object passed to error handler | | ->friend_method | macro | make a method in a container, or the entire container, a friend | | ->hash_search_error | class | failure to find a hash function for registered classes | -| ->indirect | type | tag for indirect intrusive mode | | ->method | class template | implements a method | | ->method_call_error | class | information about a failed method call | | ->method_call_error_handler | type | the type of a function called when a method call fails | @@ -175,6 +173,7 @@ This was the recommended header before version 1.3.0. Includes | ->update_methods | function | set up dispatch tables | | ->use_classes | class template | register classes and their inheritance relationships | | ->virtual_ | class template | mark a method parameter as virtual | +| ->virtual_ptr | class template | fat pointer for optimal method dispatch | | ->YOMM2_CLASS | macro | same as `register_class` (deprecated) | | ->YOMM2_CLASSES | macro | same as `register_classes` | | ->YOMM2_DECLARE | macro | same as `declare_method` | @@ -200,4 +199,3 @@ This was the recommended header before version 1.3.0. Includes | ->templates | class template | wrap templates in a `types` list | | ->types | class template | sequence of types | | ->use_definitions | class template | add batch of definitions from a generic container to methods | - diff --git a/reference.in/declare_method.md b/reference.in/declare_method.md index a71d612a..52e5845b 100644 --- a/reference.in/declare_method.md +++ b/reference.in/declare_method.md @@ -6,11 +6,11 @@ headers: yorel/yomm2/cute.hpp, yorel/yomm2/keywords.hpp --- ``` -#define declare_method(/*unspecified*/) /*unspecified*/ +#define declare_method(return-type, method, (types)) /*unspecified*/ ``` ### usage ``` -declare_method(return-type, name, (function-parameter-list)) { +declare_method(return-type, method, (types)) { ... } ``` @@ -18,20 +18,22 @@ declare_method(return-type, name, (function-parameter-list)) { Declare a method. Create an inline function `method` that returns `return-type` and takes a -parameter list consisting of the `types` without the `virtual_` marker. At least -one `types` (but not necessarily all) must be marked with `virtual_`. - -When `method` is called, the dynamic types of the arguments marked with -`virtual_` are examined, and the most specific definition compatible with -`unspecified_type...` is called. If no compatible definition exists, or if -several compatible definitions exist but none of them is more specific than all -the others, the call is illegal and an error handler is executed. By default it -writes a diagnostic on `std::cerr ` and terminates the program via `abort`. The -handler can be customized. +parameter list consisting of `types`. At least one of `types` (but not +necessarily all) must be a *virtual parameter*, i.e. in the form +[`virtual_`](virtual_.md), or [`virtual_ptr`](virtual_ptr.md). The +`virtual_` decorator is stripped from `types`. + +When `method` is called, the dynamic types of the virtual arguments are +examined, and the most specific definition compatible with `unspecified_type...` +is called. If no compatible definition exists, or if several compatible +definitions exist but none of them is more specific than all the others, the +call is illegal and an error handler is executed. By default it writes a +diagnostic on `std::cerr ` and terminates the program via `abort`. The handler +can be customized. NOTE: -* The parameter list `type` _must_ be surrounded by parentheses. +* The method parameter list _must_ be surrounded by parentheses. * The parameters in `types` consist of _just_ a type, e.g. `int` is correct but `int i` is not. diff --git a/reference.in/define_method.cpp b/reference.in/define_method.cpp index 9e4fbd00..caa1b7c0 100644 --- a/reference.in/define_method.cpp +++ b/reference.in/define_method.cpp @@ -1,6 +1,6 @@ #ifdef YOMM2_MD -hrefs: YOMM2_DEFINE +hrefs: YOMM2_DEFINE / ->home / ->reference @@ -15,11 +15,11 @@ headers: yorel/yomm2/cute.hpp, yorel/yomm2/keywords.hpp>/ ->home / ->reference entry: yorel::yomm2::virtual_ptr -entry: yorel::yomm2::make_virtual_shared headers: yorel/yomm2/core.hpp, yorel/yomm2/keywords.hpp, yorel/yomm2.hpp --- @@ -20,7 +19,7 @@ class virtual_ptr; ``` --- `virtual_ptr` is a fat pointer that consists of a pointer to an object, and a -pointer to its associated method table. +pointer to its associated method table. It can be used ### Template parameters @@ -44,38 +43,47 @@ pointer to its associated method table. ## static member functions -| | | -| --------------- | ------------------------- | -| [final](#final) | returns a new virtual_ptr | +| | | +| --------------------------------------------------- | ------------------------- | +| [`template final(Other& obj)`](#final) | returns a new virtual_ptr | ## virtual_ptr -| | | -| ------------------------------------------------------------------------------------ | --- | -| `template virtual_ptr(OtherClass& obj)` | (1) | -| `template virtual_ptr(const virtual_ptr& other)` | (2) | - ---- - -For the purposes of the description below, a reference type `OtherClass&` is -said to be compatible with a reference `Class&` if `OtherClass&` is convertible to -`Class&`. - ---- +| | | +| ---------------------------------------------------------------------------| --- | +| `template virtual_ptr(Other& obj)` | (1) | +| `template virtual_ptr(const virtual_ptr& other)` | (2) | (1) Constructs a `virtual_ptr` that contains a reference to `obj`, which must be compatible with `Class`, and a pointer to the method table corresponding to -`obj`'s *dynamic* type. If the dynamic type of `obj` is the same as `OtherClass` -(i.e. `typeid(obj) == typeid(OtherClass)`), the hash table is not looked up. +`obj`'s *dynamic* type. If the dynamic type of `obj` is the same as `Other` +(i.e. `typeid(obj) == typeid(Other)`), the hash table is not looked up. (2) Constructs a `virtual_ptr` that contains a reference to `obj`, and a pointer to the method table corresponding to `obj`'s *dynamic* type. If the dynamic type -of `obj` is the same as `OtherClass` (i.e. `typeid(obj) == typeid(OtherClass)`), +of `obj` is the same as `Other` (i.e. `typeid(obj) == typeid(Other)`), the hash table is not looked up. --- +## final + +| | | +| ---------------------------------------------------------------------------| --- | +| `template static final(Other& obj)` | (1) | + +Constructs a `virtual_ptr` that contains a reference to `obj`, which must be +compatible with `Class`, and a pointer to the method table corresponding to +`obj`'s *static* type. Neither `Class` nor `Other` are not required to be +polymorphic types. + +In debug builds, `final` compares `typeid(Class)` and `typeid(obj)`. If they are +different, `error_handler` is called with a `method_table_error` object. This +can help detect misuses of `final`, but only for polymorphic classes. + +--- + ## get | | @@ -120,22 +128,13 @@ function calls, because they do not require a hash table lookup, unlike calls made using the orthogonal mode. The lookup is performed only once, when the pointer is created via a call to the constructor, and the result is cached. -`final` assumes that the static and the dynamic types of the object are the -same, and skips the table lookup altogether. In debug builds, `final` checks -that the static and dynamic types are indeed the same by comparing -`typeid(Class)` and `typeid(obj)`. If they are different, `error_handler` is -called with a `method_table_error` object. - -`make_virtual_shared` creates the object; thus, its exact type is known, and no -table lookup (or runtime checks in debug builds) is required. - Calling `update_methods` invalidates all the existing `virtual_ptr`s. ## Example #endif -namespace direct_intrusive { +namespace ref_virtual_ptr { #ifdef YOMM2_CODE @@ -150,36 +149,71 @@ class Animal { class Dog : public Animal { }; -declare_method(void, kick, (virtual_ptr, std::ostream&)); +class Cat : public Animal { +}; -declare_method( - void, meet, (virtual_ptr, virtual_ptr, std::ostream&)); +register_classes(Animal, Dog, Cat); + +declare_method(void, kick, (virtual_ptr, std::ostream&)); define_method(void, kick, (virtual_ptr dog, std::ostream& os)) { os << "bark"; } +define_method(void, kick, (virtual_ptr cat, std::ostream& os)) { + os << "hiss"; +} + +declare_method( + void, meet, (virtual_ptr, virtual_ptr, std::ostream&)); + define_method( void, meet, (virtual_ptr a, virtual_ptr b, std::ostream& os)) { os << "wag tail"; } -register_classes(Animal, Dog); +define_method( + void, meet, (virtual_ptr a, virtual_ptr b, std::ostream& os)) { + os << "run"; +} + +define_method( + void, meet, (virtual_ptr a, virtual_ptr b, std::ostream& os)) { + os << "chase"; +} BOOST_AUTO_TEST_CASE(reference_virtual_ptr) { yorel::yomm2::update_methods(); - Dog dog; + Dog snoopy, hector; + Cat sylvester; + + std::vector> animals; + animals.emplace_back(virtual_ptr(snoopy)); + animals.emplace_back(virtual_ptr(sylvester)); + animals.emplace_back(virtual_ptr(hector)); { boost::test_tools::output_test_stream os; - kick(dog, os); + kick(animals[0], os); BOOST_CHECK(os.is_equal("bark")); } { boost::test_tools::output_test_stream os; - meet(dog, dog, os); + kick(animals[1], os); + BOOST_CHECK(os.is_equal("hiss")); + } + + { + boost::test_tools::output_test_stream os; + meet(animals[0], animals[1], os); + BOOST_CHECK(os.is_equal("chase")); + } + + { + boost::test_tools::output_test_stream os; + meet(animals[0], animals[2], os); BOOST_CHECK(os.is_equal("wag tail")); } } @@ -218,6 +252,102 @@ A call to `meet` compiles to: jmp rax ``` +`final` can be used to add polymorphic behavior to non-polymorphic classes. It +is the responsibility of the caller to ensure that the static type of the object +is, indeed, its exact type. The check + + +## Example + +#endif + +namespace ref_virtual_ptr_final { + +#ifdef YOMM2_CODE + +using yorel::yomm2::virtual_ptr; + +class Animal { + // note: no virtual functions +}; + +class Dog : public Animal { +}; + +class Cat : public Animal { +}; + +register_classes(Animal, Dog, Cat); + +declare_method(void, kick, (virtual_ptr, std::ostream&)); + +define_method(void, kick, (virtual_ptr dog, std::ostream& os)) { + os << "bark"; +} + +define_method(void, kick, (virtual_ptr cat, std::ostream& os)) { + os << "hiss"; +} + +declare_method( + void, meet, (virtual_ptr, virtual_ptr, std::ostream&)); + +define_method( + void, meet, (virtual_ptr a, virtual_ptr b, std::ostream& os)) { + os << "wag tail"; +} + +define_method( + void, meet, (virtual_ptr a, virtual_ptr b, std::ostream& os)) { + os << "run"; +} + +define_method( + void, meet, (virtual_ptr a, virtual_ptr b, std::ostream& os)) { + os << "chase"; +} + +BOOST_AUTO_TEST_CASE(reference_virtual_ptr_final) { + yorel::yomm2::update_methods(); + + Dog snoopy, hector; + Cat sylvester; + + std::vector> animals; + animals.emplace_back(virtual_ptr::final(snoopy)); + animals.emplace_back(virtual_ptr::final(sylvester)); + animals.emplace_back(virtual_ptr::final(hector)); + + { + boost::test_tools::output_test_stream os; + kick(animals[0], os); + BOOST_CHECK(os.is_equal("bark")); + } + + { + boost::test_tools::output_test_stream os; + kick(animals[1], os); + BOOST_CHECK(os.is_equal("hiss")); + } + + { + boost::test_tools::output_test_stream os; + meet(animals[0], animals[1], os); + BOOST_CHECK(os.is_equal("chase")); + } + + { + boost::test_tools::output_test_stream os; + meet(animals[0], animals[2], os); + BOOST_CHECK(os.is_equal("wag tail")); + } +} + +#endif +} + +#ifdef YOMM2_MD + [^1]: The only reason why `virtual_ptr` is not called `virtual_ref` is to save the name for the time when C++ will support a user-defined dot operator. diff --git a/reference/README.md b/reference/README.md index 8f3943dd..40c6e9c2 100644 --- a/reference/README.md +++ b/reference/README.md @@ -12,7 +12,7 @@ Yuriy Solodkyy, and Bjarne Stroustrup. This implementation diverges from the paper on the following points: * A "base-method" is called a "method declaration" in YOMM2, and an "overrider" - is called a "method definition". This is because + is called a "method definition". This is because * YOMM2 has a mechanism (`next`) to call the next most specialised method. * The paper allows only references for virtual parameters. YOMM2 also allows pointers and smart pointers. @@ -153,12 +153,10 @@ This was the recommended header before version 1.3.0. Includes | [define_method](define_method.md) | macro | add a definition to a method | | [define_method_inline](define_method_inline.md) | macro | add an definition to a method in a container, and make it inline | | [derived](intrusive_modes.md) | class template | helper for intrusive modes | -| [direct](intrusive_modes.md) | type | tag for direct intrusive mode | | [error_handler_type](set_error_handler.md) | type | handler function | | [error_type](set_error_handler.md) | variant | object passed to error handler | | [friend_method](friend_method.md) | macro | make a method in a container, or the entire container, a friend | | [hash_search_error](set_error_handler.md) | class | failure to find a hash function for registered classes | -| [indirect](intrusive_modes.md) | type | tag for indirect intrusive mode | | [method](method.md) | class template | implements a method | | [method_call_error](method_call_error.md) | class | information about a failed method call | | [method_call_error_handler](method_call_error.md) | type | the type of a function called when a method call fails | @@ -175,6 +173,7 @@ This was the recommended header before version 1.3.0. Includes | [update_methods](update_methods.md) | function | set up dispatch tables | | [use_classes](use_classes.md) | class template | register classes and their inheritance relationships | | [virtual_](virtual_.md) | class template | mark a method parameter as virtual | +| [virtual_ptr](virtual_ptr.md) | class template | fat pointer for optimal method dispatch | | [YOMM2_CLASS](register_class.md) | macro | same as `register_class` (deprecated) | | [YOMM2_CLASSES](None) | macro | same as `register_classes` | | [YOMM2_DECLARE](declare_method.md) | macro | same as `declare_method` | @@ -200,4 +199,3 @@ This was the recommended header before version 1.3.0. Includes | [templates](templates.md) | class template | wrap templates in a `types` list | | [types](types.md) | class template | sequence of types | | [use_definitions](use_definitions.md) | class template | add batch of definitions from a generic container to methods | - diff --git a/reference/declare_method.md b/reference/declare_method.md index 21cb3d3c..e5b80898 100644 --- a/reference/declare_method.md +++ b/reference/declare_method.md @@ -6,11 +6,11 @@ --- ``` -#define declare_method(/*unspecified*/) /*unspecified*/ +#define declare_method(return-type, method, (types)) /*unspecified*/ ``` ### usage ``` -declare_method(return-type, name, (function-parameter-list)) { +declare_method(return-type, method, (types)) { ... } ``` @@ -18,20 +18,22 @@ declare_method(return-type, name, (function-parameter-list)) { Declare a method. Create an inline function `method` that returns `return-type` and takes a -parameter list consisting of the `types` without the `virtual_` marker. At least -one `types` (but not necessarily all) must be marked with `virtual_`. - -When `method` is called, the dynamic types of the arguments marked with -`virtual_` are examined, and the most specific definition compatible with -`unspecified_type...` is called. If no compatible definition exists, or if -several compatible definitions exist but none of them is more specific than all -the others, the call is illegal and an error handler is executed. By default it -writes a diagnostic on `std::cerr ` and terminates the program via `abort`. The -handler can be customized. +parameter list consisting of `types`. At least one of `types` (but not +necessarily all) must be a *virtual parameter*, i.e. in the form +[`virtual_`](virtual_.md), or [`virtual_ptr`](virtual_ptr.md). The +`virtual_` decorator is stripped from `types`. + +When `method` is called, the dynamic types of the virtual arguments are +examined, and the most specific definition compatible with `unspecified_type...` +is called. If no compatible definition exists, or if several compatible +definitions exist but none of them is more specific than all the others, the +call is illegal and an error handler is executed. By default it writes a +diagnostic on `std::cerr ` and terminates the program via `abort`. The handler +can be customized. NOTE: -* The parameter list `type` _must_ be surrounded by parentheses. +* The method parameter list _must_ be surrounded by parentheses. * The parameters in `types` consist of _just_ a type, e.g. `int` is correct but `int i` is not. diff --git a/reference/define_method.md b/reference/define_method.md index 28f67fac..5ba75ea3 100644 --- a/reference/define_method.md +++ b/reference/define_method.md @@ -15,11 +15,11 @@ ### usage ``` -define_method(return-type, name, (function-parameter-list)) { +define_method(return-type, name, (method-parameter-list)) { ... } -define_method(container, return-type, name, (function-parameter-list)) { +define_method(container, return-type, name, (method-parameter-list)) { ... } ``` @@ -27,11 +27,11 @@ define_method(container, return-type, name, (function-parameter-list)) { Add a definition to a method. Locate a method with the same name, with a signature compatible with -`function-parameter-list`, and add the definition to the method's list of +`method-parameter-list`, and add the definition to the method's list of definitions. The method must exist and must be unique. `return-type` must be covariant with the method's return type. `return-type` may be `auto`. -The types of the arguments are _not_ marked with `virtual_`. +The types of the arguments must _not_ be decorated with `virtual_`. Inside the block, a function pointer named `next` points to the next most specific definition, if one exists, and it is unique. Otherwise, `next` points diff --git a/reference/virtual_ptr.md b/reference/virtual_ptr.md index 9c831aa4..de8b2a0e 100644 --- a/reference/virtual_ptr.md +++ b/reference/virtual_ptr.md @@ -2,7 +2,6 @@ / [home](/README.md) / [reference](/reference/README.md) **yorel::yomm2::virtual_ptr**
-**yorel::yomm2::make_virtual_shared**
defined in , also provided by, --- @@ -13,7 +12,7 @@ class virtual_ptr; ``` --- `virtual_ptr` is a fat pointer that consists of a pointer to an object, and a -pointer to its associated method table. +pointer to its associated method table. It can be used ### Template parameters @@ -37,38 +36,47 @@ pointer to its associated method table. ## static member functions -| | | -| --------------- | ------------------------- | -| [final](#final) | returns a new virtual_ptr | +| | | +| --------------------------------------------------- | ------------------------- | +| [`template final(Other& obj)`](#final) | returns a new virtual_ptr | ## virtual_ptr -| | | -| ------------------------------------------------------------------------------------ | --- | -| `template virtual_ptr(OtherClass& obj)` | (1) | -| `template virtual_ptr(const virtual_ptr& other)` | (2) | - ---- - -For the purposes of the description below, a reference type `OtherClass&` is -said to be compatible with a reference `Class&` if `OtherClass&` is convertible to -`Class&`. - ---- +| | | +| ---------------------------------------------------------------------------| --- | +| `template virtual_ptr(Other& obj)` | (1) | +| `template virtual_ptr(const virtual_ptr& other)` | (2) | (1) Constructs a `virtual_ptr` that contains a reference to `obj`, which must be compatible with `Class`, and a pointer to the method table corresponding to -`obj`'s *dynamic* type. If the dynamic type of `obj` is the same as `OtherClass` -(i.e. `typeid(obj) == typeid(OtherClass)`), the hash table is not looked up. +`obj`'s *dynamic* type. If the dynamic type of `obj` is the same as `Other` +(i.e. `typeid(obj) == typeid(Other)`), the hash table is not looked up. (2) Constructs a `virtual_ptr` that contains a reference to `obj`, and a pointer to the method table corresponding to `obj`'s *dynamic* type. If the dynamic type -of `obj` is the same as `OtherClass` (i.e. `typeid(obj) == typeid(OtherClass)`), +of `obj` is the same as `Other` (i.e. `typeid(obj) == typeid(Other)`), the hash table is not looked up. --- +## final + +| | | +| ---------------------------------------------------------------------------| --- | +| `template static final(Other& obj)` | (1) | + +Constructs a `virtual_ptr` that contains a reference to `obj`, which must be +compatible with `Class`, and a pointer to the method table corresponding to +`obj`'s *static* type. Neither `Class` nor `Other` are not required to be +polymorphic types. + +In debug builds, `final` compares `typeid(Class)` and `typeid(obj)`. If they are +different, `error_handler` is called with a `method_table_error` object. This +can help detect misuses of `final`, but only for polymorphic classes. + +--- + ## get | | @@ -113,15 +121,6 @@ function calls, because they do not require a hash table lookup, unlike calls made using the orthogonal mode. The lookup is performed only once, when the pointer is created via a call to the constructor, and the result is cached. -`final` assumes that the static and the dynamic types of the object are the -same, and skips the table lookup altogether. In debug builds, `final` checks -that the static and dynamic types are indeed the same by comparing -`typeid(Class)` and `typeid(obj)`. If they are different, `error_handler` is -called with a `method_table_error` object. - -`make_virtual_shared` creates the object; thus, its exact type is known, and no -table lookup (or runtime checks in debug builds) is required. - Calling `update_methods` invalidates all the existing `virtual_ptr`s. ## Example @@ -139,36 +138,71 @@ class Animal { class Dog : public Animal { }; -declare_method(void, kick, (virtual_ptr, std::ostream&)); +class Cat : public Animal { +}; -declare_method( - void, meet, (virtual_ptr, virtual_ptr, std::ostream&)); +register_classes(Animal, Dog, Cat); + +declare_method(void, kick, (virtual_ptr, std::ostream&)); define_method(void, kick, (virtual_ptr dog, std::ostream& os)) { os << "bark"; } +define_method(void, kick, (virtual_ptr cat, std::ostream& os)) { + os << "hiss"; +} + +declare_method( + void, meet, (virtual_ptr, virtual_ptr, std::ostream&)); + define_method( void, meet, (virtual_ptr a, virtual_ptr b, std::ostream& os)) { os << "wag tail"; } -register_classes(Animal, Dog); +define_method( + void, meet, (virtual_ptr a, virtual_ptr b, std::ostream& os)) { + os << "run"; +} + +define_method( + void, meet, (virtual_ptr a, virtual_ptr b, std::ostream& os)) { + os << "chase"; +} BOOST_AUTO_TEST_CASE(reference_virtual_ptr) { yorel::yomm2::update_methods(); - Dog dog; + Dog snoopy, hector; + Cat sylvester; + + std::vector> animals; + animals.emplace_back(virtual_ptr(snoopy)); + animals.emplace_back(virtual_ptr(sylvester)); + animals.emplace_back(virtual_ptr(hector)); { boost::test_tools::output_test_stream os; - kick(dog, os); + kick(animals[0], os); BOOST_CHECK(os.is_equal("bark")); } { boost::test_tools::output_test_stream os; - meet(dog, dog, os); + kick(animals[1], os); + BOOST_CHECK(os.is_equal("hiss")); + } + + { + boost::test_tools::output_test_stream os; + meet(animals[0], animals[1], os); + BOOST_CHECK(os.is_equal("chase")); + } + + { + boost::test_tools::output_test_stream os; + meet(animals[0], animals[2], os); BOOST_CHECK(os.is_equal("wag tail")); } } @@ -195,6 +229,95 @@ A call to `meet` compiles to: jmp rax ``` +`final` can be used to add polymorphic behavior to non-polymorphic classes. It +is the responsibility of the caller to ensure that the static type of the object +is, indeed, its exact type. The check + + +## Example + + +```c++ +using yorel::yomm2::virtual_ptr; + +class Animal { + // note: no virtual functions +}; + +class Dog : public Animal { +}; + +class Cat : public Animal { +}; + +register_classes(Animal, Dog, Cat); + +declare_method(void, kick, (virtual_ptr, std::ostream&)); + +define_method(void, kick, (virtual_ptr dog, std::ostream& os)) { + os << "bark"; +} + +define_method(void, kick, (virtual_ptr cat, std::ostream& os)) { + os << "hiss"; +} + +declare_method( + void, meet, (virtual_ptr, virtual_ptr, std::ostream&)); + +define_method( + void, meet, (virtual_ptr a, virtual_ptr b, std::ostream& os)) { + os << "wag tail"; +} + +define_method( + void, meet, (virtual_ptr a, virtual_ptr b, std::ostream& os)) { + os << "run"; +} + +define_method( + void, meet, (virtual_ptr a, virtual_ptr b, std::ostream& os)) { + os << "chase"; +} + +BOOST_AUTO_TEST_CASE(reference_virtual_ptr_final) { + yorel::yomm2::update_methods(); + + Dog snoopy, hector; + Cat sylvester; + + std::vector> animals; + animals.emplace_back(virtual_ptr::final(snoopy)); + animals.emplace_back(virtual_ptr::final(sylvester)); + animals.emplace_back(virtual_ptr::final(hector)); + + { + boost::test_tools::output_test_stream os; + kick(animals[0], os); + BOOST_CHECK(os.is_equal("bark")); + } + + { + boost::test_tools::output_test_stream os; + kick(animals[1], os); + BOOST_CHECK(os.is_equal("hiss")); + } + + { + boost::test_tools::output_test_stream os; + meet(animals[0], animals[1], os); + BOOST_CHECK(os.is_equal("chase")); + } + + { + boost::test_tools::output_test_stream os; + meet(animals[0], animals[2], os); + BOOST_CHECK(os.is_equal("wag tail")); + } +} +``` + + [^1]: The only reason why `virtual_ptr` is not called `virtual_ref` is to save the name for the time when C++ will support a user-defined dot operator. diff --git a/src/yomm2.cpp b/src/yomm2.cpp index a353bd0e..dca0f1d3 100644 --- a/src/yomm2.cpp +++ b/src/yomm2.cpp @@ -1068,7 +1068,7 @@ void runtime::print(const dispatch_stats_t& stats) const { void default_method_call_error_handler( const method_call_error& error, size_t arity, const ti_ptr ti_ptrs[]) { - if constexpr (bool(trace_enabled)) { + if constexpr (bool(debug)) { const char* explanation[] = { "no applicable definition", "ambiguous call"}; std::cerr << explanation[error.code - resolution_error::no_definition] @@ -1097,14 +1097,14 @@ void default_error_handler(const error_type& error_v) { } if (auto error = std::get_if(&error_v)) { - if constexpr (bool(trace_enabled)) { + if constexpr (bool(debug)) { std::cerr << "unknown class " << error->ti->name() << "\n"; } abort(); } if (auto error = std::get_if(&error_v)) { - if constexpr (bool(trace_enabled & TRACE_RUNTIME)) { + if constexpr (bool(debug)) { std::cerr << "invalid method table for " << error->ti->name() << "\n"; } @@ -1112,7 +1112,7 @@ void default_error_handler(const error_type& error_v) { } if (auto error = std::get_if(&error_v)) { - if constexpr (bool(trace_enabled & TRACE_RUNTIME)) { + if constexpr (bool(debug)) { std::cerr << "could not find hash factors after " << error->attempts << " in " << error->duration.count() << "s using " << error->buckets << " buckets\n"; diff --git a/tests/benchmarks.cpp b/tests/benchmarks.cpp index 1e493854..d519e53c 100644 --- a/tests/benchmarks.cpp +++ b/tests/benchmarks.cpp @@ -573,7 +573,9 @@ int main(int argc, char** argv) { } } - populations[0]->dispatcher()(); + auto pop = populations[0]; + pop->dispatcher()(); + pop->dispatcher()(); #if !defined(NDEBUG)