diff --git a/README.md b/README.md index e7cdc28..530e314 100644 --- a/README.md +++ b/README.md @@ -2,41 +2,117 @@ [![Proxy-CI](https://github.com/microsoft/proxy/actions/workflows/pipeline-ci.yml/badge.svg)](https://github.com/microsoft/proxy/actions/workflows/pipeline-ci.yml) -Do you want to facilitate lifetime management and maintenance of polymorphic objects in C++? +Are you looking to simplify the lifetime management and maintenance of polymorphic objects in C++? -Do you want to be able to write polymorphic code in C++ as easily as in languages with GC (like Java or C#), while still having excellent runtime performance? +Do you want to write polymorphic code in C++ as easily as in [GC languages](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)) like Java or C#, without sacrificing performance? Have you tried other polymorphic programming libraries in C++ but found them deficient? -If so, this library is for you. 😉 +If so, this library is for you. -For decades, object-based virtual table has been a de facto implementation of runtime polymorphism in many (compiled) programming languages. There are many drawbacks in this mechanism, including life management (because each object may have different size and ownership) and reflection (because it is hard to balance between usability and memory allocation). To workaround these drawbacks, some languages like Java or C# choose to sacrifice performance by introducing GC to facilitate lifetime management, and JIT-compile the source code at runtime to generate full metadata. We improved the theory and implemented as a C++ library without sacrificing performance, proposed to merge into the C++ standard. +## Our Mission -The "proxy" is a single-header, cross-platform C++ library that Microsoft uses to make runtime polymorphism easier to implement and faster. Please find the design details at https://wg21.link/p3086. +"Proxy" is a modern C++ library that helps you use polymorphism (a way to use different types of objects interchangeably) without needing inheritance. -## Quick start +"Proxy" was created by Microsoft engineers and has been used in the Windows operating system since 2022. For many years, using inheritance was the main way to achieve polymorphism in C++. However, new programming languages like [Rust](https://doc.rust-lang.org/book/ch10-02-traits.html) offer better ways to do this. We have improved our understanding of object-oriented programming and decided to use *pointers* in C++ as the foundation for "Proxy". Specifically, the "Proxy" library is designed to be: -The "proxy" is a header-only C++20 library. Once you set the language level of your compiler not earlier than C++20 and get the header file ([proxy.h](proxy.h)), you are all set. You can also install the library via [vcpkg](https://github.com/microsoft/vcpkg/), which is a C++ library manager invented by Microsoft, by searching for "proxy" (see [vcpkg.io](https://vcpkg.io/en/package/proxy)). +- **Portable**: "Proxy" was implemented as a single-header library in standard C++20. It can be used on any platform while the compiler supports C++20. The majority of the library is [freestanding](https://en.cppreference.com/w/cpp/freestanding), making it feasible for embedded engineering or kernel design of an operating system. +- **Non-intrusive**: An implementation type is no longer required to inherit from an abstract binding. +- **Well-managed**: "Proxy" provides a GC-like capability that manages the lifetimes of different objects efficiently without the need for an actual garbage collector. +- **Fast**: With typical compiler optimizations, "Proxy" produces high-quality code that is as good as or better than hand-written code. In many cases, "Proxy" performs better than traditional inheritance-based approaches, especially in managing the lifetimes of objects. +- **Accessible**: Learned from user feedback, accessibility has been significantly improved in "Proxy 3" with intuitive syntax, good IDE compatibility, and accurate diagnostics. +- **Flexible**: Not only member functions, the "abstraction" of "Proxy" allows *any* expression to be polymorphic, including free functions, operators, conversions, etc. Different abstractions can be freely composed on demand. Performance tuning is supported for experts to balance between extensibility and performance. -The majority of the library is defined in namespace `pro`. Some macros are provided (currently not included in the proposal for standardization) to simplify the definition of `proxy` prior to C++26. Here is a demo showing how to use this library to implement runtime polymorphism in a different way from the traditional inheritance-based approach: +Please refer to the [Proxy's Frequently Asked Questions](https://microsoft.github.io/proxy/docs/faq.html) for more background, and refer to the [specifications](https://microsoft.github.io/proxy/docs/specifications.html) for more technical details. + +## Quick Start + +"Proxy" is a header-only C++20 library. To use the library, make sure your compiler meets the [minimum requirements](#compiler-req) and just include the header file [proxy.h](https://github.com/microsoft/proxy/blob/main/proxy.h) in your source code. Alternatively, you can install the library via [vcpkg](https://learn.microsoft.com/en-us/vcpkg/get_started/overview) or [conan](https://conan.io/), by searching for "proxy" (see [vcpkg.io](https://vcpkg.io/en/package/proxy) and [conan.io](https://conan.io/center/recipes/proxy)). + +### Hello World + +Let's get started with the following "Hello World" example: + +```cpp +#include +#include + +#include "proxy.h" + +struct Streamable : pro::facade_builder + ::add_convention, std::ostream&(std::ostream& out) const> + ::build {}; + +int main() { + std::string str = "Hello World"; + pro::proxy p1 = &str; + std::cout << "p1 = " << *p1 << "\n"; // Prints: "p1 = Hello World" + + pro::proxy p2 = std::make_unique(123); + std::cout << "p2 = " << *p2 << "\n"; // Prints: "p2 = 123" + + pro::proxy p3 = pro::make_proxy(3.14); + std::cout << "p3 = " << *p3 << "\n"; // Prints: "p3 = 3.14" +} +``` + +Here is a step-by-step explanation: + +- `#include `: For [`std::cout`](https://en.cppreference.com/w/cpp/io/cout). +- `#include `: For [`std::string`](https://en.cppreference.com/w/cpp/string/basic_string). +- `#include "proxy.h"`: For the "Proxy" library. Most of the facilities of the library are defined in namespace `pro`. If the library is consumed via [vcpkg](https://learn.microsoft.com/en-us/vcpkg/get_started/overview) or [conan](https://conan.io/), this line should be changed into `#include `. +- `struct Streamable : pro::facade_builder ... ::build {}`: Defines a facade type `Streamable`. The term "facade", formally defined as the [*ProFacade* requirements](https://microsoft.github.io/proxy/docs/ProFacade.html), is how the "Proxy" library models runtime abstraction. Specifically, + - [`pro::facade_builder`](https://microsoft.github.io/proxy/docs/basic_facade_builder.html): Provides capability to build a facade type at compile-time. + - [`add_convention`](https://microsoft.github.io/proxy/docs/basic_facade_builder/add_convention.html): Adds a generalized "calling convention", defined by a "dispatch" and several "overloads", to the build context. + - [`pro::operator_dispatch`](https://microsoft.github.io/proxy/docs/operator_dispatch.html)`<"<<", true>`: Specifies a dispatch for operator `<<` expressions where the primary operand (`proxy`) is on the right-hand side (specified by the second template parameter `true`). Note that polymorphism in the "Proxy" library is defined by expressions rather than member functions, which is different from C++ virtual functions or other OOP languages. + - `std::ostream&(std::ostream& out) const`: The signature of the calling convention, similar with [`std::move_only_function`](https://en.cppreference.com/w/cpp/utility/functional/move_only_function). `const` specifies that the primary operand is `const`. + - [`build`](https://microsoft.github.io/proxy/docs/basic_facade_builder/build.html): Builds the context into a facade type. +- [`pro::proxy`](https://microsoft.github.io/proxy/docs/proxy.html)` p1 = &str`: Creates a `proxy` object from a raw pointer of `std::string`. `p1` behaves like a raw pointer, and does not have ownership of the underlying `std::string`. If the lifetime of `str` ends before `p1`, `p1` becomes dangling. +- `std::cout << *p1`: This is how it works. It prints "Hello World" because the calling convention is defined in the facade `Streamable`, so it works as if by calling `std::cout << str`. +- [`pro::proxy`](https://microsoft.github.io/proxy/docs/proxy.html)` p2 = `[`std::make_unique`](https://en.cppreference.com/w/cpp/memory/unique_ptr/make_unique)`(123)`: Creates a [`std::unique_ptr`](https://en.cppreference.com/w/cpp/memory/unique_ptr)`` and converts to a `proxy`. Different from `p1`, `p2` has ownership of the underlying `int` because it is instantiated from a value of `std::unique_ptr`, and will call the destructor of `std::unique_ptr` when `p2` is destroyed, while `p1` does not have ownership of the underlying `int` because it is instantiated from a raw pointer. `p1` and `p2` are of the same type `pro::proxy`, which means you can have a function that returns `pro::proxy` without exposing any information about the implementation details to its caller. +- `std::cout << *p2`: Prints "123" with no surprise. +- [`pro::proxy`](https://microsoft.github.io/proxy/docs/proxy.html)` p3 = `[`pro::make_proxy`](https://microsoft.github.io/proxy/docs/make_proxy.html)`(3.14)`: Creates a `proxy` from a `double` without specifying the underlying pointer type. Specifically, + - Similar with `p2`, `p3` also has ownership of the underlying `double` value, but can effectively avoid heap allocation. + - Since the size of the underlying type (`double`) is known to be small (on major 32- or 64-bit platforms), [`pro::make_proxy`](https://microsoft.github.io/proxy/docs/make_proxy.html) realizes the fact at compile-time, and falls back to [`pro::make_proxy_inplace`](https://microsoft.github.io/proxy/docs/make_proxy_inplace.html), which guarantees no heap allocation. + - The "Proxy" library explicitly defines when heap allocation occurs or not to avoid users falling into performance hell, which is different from [`std::function`](https://en.cppreference.com/w/cpp/utility/functional/function) and other existing polymorphic wrappers in the standard. + +- `std::cout << *p3`: Prints "3.14" with no surprise. +- When `main` returns, `p2` and `p3` will destroy the underlying objects, while `p1` does nothing because it holds a raw pointer that does not have ownership of the underlying `std::string`. + +### More Expressions + +In addition to the operator expressions demonstrated in the previous example, the library supports almost all forms of expressions in C++ and can make them polymorphic. Specifically, + +- [macro `PRO_DEF_MEM_DISPATCH`](https://microsoft.github.io/proxy/docs/PRO_DEF_MEM_DISPATCH.html): Defines a dispatch type for member function call expressions. +- [macro `PRO_DEF_FREE_DISPATCH`](https://microsoft.github.io/proxy/docs/PRO_DEF_FREE_DISPATCH.html): Defines a dispatch type for free function call expressions. +- [class template `pro::operator_dispatch`](https://microsoft.github.io/proxy/docs/operator_dispatch.html): Dispatch type for operator expressions. +- [class template `pro::conversion_dispatch`](https://microsoft.github.io/proxy/docs/conversion_dispatch.html): Dispatch type for conversion expressions. + +Note that some facilities are provided as macro, because C++ templates today do not support generating a function with an arbitrary name. Here is another example that makes member function call expressions polymorphic: ```cpp -// Specifications of abstraction +#include +#include + +#include "proxy.h" + PRO_DEF_MEM_DISPATCH(MemDraw, Draw); PRO_DEF_MEM_DISPATCH(MemArea, Area); struct Drawable : pro::facade_builder ::add_convention ::add_convention + ::support_copy ::build {}; -// Implementation class Rectangle { public: - void Draw(std::ostream& out) const - { out << "{Rectangle: width = " << width_ << ", height = " << height_ << "}"; } - void SetWidth(double width) { width_ = width; } - void SetHeight(double height) { height_ = height; } + Rectangle(double width, double height) : width_(width), height_(height) {} + Rectangle(const Rectangle&) = default; + + void Draw(std::ostream& out) const { + out << "{Rectangle: width = " << width_ << ", height = " << height_ << "}"; + } double Area() const noexcept { return width_ * height_; } private: @@ -44,141 +120,57 @@ class Rectangle { double height_; }; -// Client - Consumer std::string PrintDrawableToString(pro::proxy p) { std::stringstream result; - result << "shape = "; - p->Draw(result); // Polymorphic call - result << ", area = " << p->Area(); // Polymorphic call + result << "entity = "; + p->Draw(result); + result << ", area = " << p->Area(); return std::move(result).str(); } -// Client - Producer -pro::proxy CreateRectangleAsDrawable(int width, int height) { - Rectangle rect; - rect.SetWidth(width); - rect.SetHeight(height); - return pro::make_proxy(rect); -} -``` - -Here is another demo showing how to define overloads in a dispatch: just to add any number of signatures in the definition of a dispatch. - -```cpp -// Specifications of abstraction -PRO_DEF_MEM_DISPATCH(MemLog, Log); - -struct Logger : pro::facade_builder - ::add_convention - ::build {}; - -// Client - Consumer -void MyVerboseFunction(pro::proxy logger) { - logger->Log("hello"); - try { - throw std::runtime_error{"runtime error!"}; - } catch (const std::exception& e) { - logger->Log("world", e); - } -} - -// Implementation -struct MyLogger { - void Log(const char* s) { - printf("[INFO] %s\n", s); - } - void Log(const char* s, const std::exception& e) { - printf("[ERROR] %s (exception info: %s)\n", s, e.what()); - } -}; - -// Client - Producer int main() { - MyLogger logger; - MyVerboseFunction(&logger); - return 0; + pro::proxy p = pro::make_proxy(3, 5); + std::string str = PrintDrawableToString(p); + std::cout << str << "\n"; // Prints: "entity = {Rectangle: width = 3, height = 5}, area = 15" } ``` -By design, the body of a dispatch could be any code. While member function is one useful pattern supported by macro `PRO_DEF_MEM_DISPATCH`, free function and operators are also supported with macros `PRO_DEF_FREE_DISPATCH` and `PRO_DEF_OPERATOR_DISPATCH`. The following example uses `PRO_DEF_OPERATOR_DISPATCH` to implement similar function wrapper as `std::function` and `std::move_only_function` and supports multiple overloads: - -```cpp -// Specifications of abstraction -template -struct MovableCallable : pro::facade_builder - ::add_convention, Overloads...> - ::build {}; +Here is a step-by-step explanation: -template -struct CopyableCallable : pro::facade_builder - ::support_copy - ::add_facade> // Facade inheritance - ::build {}; +- `#include `: For [`std::cout`](https://en.cppreference.com/w/cpp/io/cout). +- `#include `: For [`std::stringstream`](https://en.cppreference.com/w/cpp/io/basic_stringstream). +- `#include "proxy.h"`: For the "Proxy" library. +- [`PRO_DEF_MEM_DISPATCH`](https://microsoft.github.io/proxy/docs/PRO_DEF_MEM_DISPATCH.html)`(MemDraw, Draw)`: Defines a dispatch type `MemDraw` for expressions of calling member function `Draw`. +- [`PRO_DEF_MEM_DISPATCH`](https://microsoft.github.io/proxy/docs/PRO_DEF_MEM_DISPATCH.html)`(MemArea, Area)`: Defines a dispatch type `MemArea` for expressions of calling member function `Area`. +- `struct Drawable : pro::facade_builder ... ::build {}`: Defines a facade type `Drawable`. Specifically, + - [`add_convention`](https://microsoft.github.io/proxy/docs/basic_facade_builder/add_convention.html): Adds calling conventions to the build context. + - [`support_copy`](https://microsoft.github.io/proxy/docs/basic_facade_builder/support_copy.html)`<`[`pro::constraint_level`](https://microsoft.github.io/proxy/docs/constraint_level.html)`::nontrivial>`: Specifies the underlying pointer type shall be copyable, which also makes the resulting `proxy` type copyable. +- `class Rectangle`: An implementation of `Drawable`. +- Function `PrintDrawableToString`: Converts a `Drawable` into a `std::string`. Note that this is a function rather than a function template, which means it can generate [ABI](https://en.wikipedia.org/wiki/Application_binary_interface) in a larger build system. +- `pro::proxy p = pro::make_proxy(3, 5)`: Creates a `proxy` object containing a `Rectangle`. +- `std::string str = PrintDrawableToString(p)`: Converts `p` into a `std::string`, implicitly creates a copy of `p`. +- `std::cout << str`: Prints the string. -// MyFunction has similar functionality as std::function but supports multiple overloads -// MyMoveOnlyFunction has similar functionality as std::move_only_function but supports multiple overloads -template -using MyFunction = pro::proxy>; -template -using MyMoveOnlyFunction = pro::proxy>; +### Other Useful Features -int main() { - auto f = [](auto&&... v) { - printf("f() called. Args: "); - ((std::cout << v << ":" << typeid(decltype(v)).name() << ", "), ...); - puts(""); - }; - MyFunction p0{&f}; - (*p0)(123); // Prints "f() called. Args: 123:i," (assuming GCC) - MyMoveOnlyFunction p1{&f}; - (*p1)(); // Prints "f() called. Args:" - (*p1)(456); // Prints "f() called. Args: 456:i," - (*p1)(1.2); // Prints "f() called. Args: 1.2:d," - return 0; -} -``` +The "Proxy" library is a self-contained solution for runtime polymorphism in C++. There are many other capabilities documented in the [specifications](https://microsoft.github.io/proxy/docs/specifications.html). In addition to the features mentioned above, here is a curated list of the most popular features based on user feedback: -Please find more details and discussions in the spec. The complete version of the "drawable" demo could be found in [tests/proxy_integration_tests.cpp](tests/proxy_integration_tests.cpp) (also available on [Compiler Explorer](https://godbolt.org/z/zro9dd3Eo)). +- **Overloading**: [`facade_builder::add_convention`](https://microsoft.github.io/proxy/docs/basic_facade_builder/add_convention.html) is more powerful than demonstrated above. It can take any number of overload types (formally, any type meeting the [*ProOverload* requirements](https://microsoft.github.io/proxy/docs/ProOverload.html)) and perform standard overload resolution when invoking a `proxy`. +- **Facade composition**: [`facade_builder::add_facade`](https://microsoft.github.io/proxy/docs/basic_facade_builder/add_facade.html) allows flexible composition of different abstractions. +- **Weak dispatch**: When an object does not implement a convention, and we do not want it to trigger a hard compile error, it is allowed to define a "weak dispatch" with [macro `PRO_DEF_WEAK_DISPATCH`](https://microsoft.github.io/proxy/docs/PRO_DEF_WEAK_DISPATCH.html) from an existing dispatch type and a default implementation. +- **Allocator awareness**: [function template `allocate_proxy`](https://microsoft.github.io/proxy/docs/allocate_proxy.html) is able to create a `proxy` from a value with any custom allocator. In C++11, [`std::function`](https://en.cppreference.com/w/cpp/utility/functional/function) and [`std::packaged_task`](https://en.cppreference.com/w/cpp/thread/packaged_task) had constructors that accepted custom allocators for performance tuning, but these were [removed in C++17](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0302r1.html) because "the semantics are unclear, and there are technical issues with storing an allocator in a type-erased context and then recovering that allocator later for any allocations needed during copy assignment". These issues do not apply to `allocate_proxy`. +- **Configurable constraints**: [`facade_builder`](https://microsoft.github.io/proxy/docs/basic_facade_builder.html) provides full support for constraints configuration, including memory layout (by [`restrict_layout`](https://microsoft.github.io/proxy/docs/basic_facade_builder/restrict_layout.html)), copyability (by [`support_copy`](https://microsoft.github.io/proxy/docs/basic_facade_builder/support_copy.html)), relocatability (by [`support_relocation`](https://microsoft.github.io/proxy/docs/basic_facade_builder/support_relocation.html)), and destructibility (by [`support_destruction`](https://microsoft.github.io/proxy/docs/basic_facade_builder/support_destruction.html)). +- **Reflection**: `proxy` supports type-based compile-time reflection for runtime queries. Please refer to [`facade_builder::add_reflection`](https://microsoft.github.io/proxy/docs/basic_facade_builder/add_reflection.html) and [function template `proxy_reflect`](https://microsoft.github.io/proxy/docs/proxy_reflect.html) for more details. -## Minimum requirements for compilers +## Minimum Requirements for Compilers | Family | Minimum version | Required flags | -| --- | --- | --- | -| clang | 15.0.0 | -std=c++20 | -| gcc | 11.2 | -std=c++20 | -| MSVC | 19.30 | /std:c++20 | - -## Use `proxy` with CMake and [Vcpkg](https://github.com/microsoft/vcpkg) - -See more details in [samples](./samples) - -1. Set up vcpkg manifest -``` -{ - "name": "", - "version": "0.1.0", - "dependencies": [ - { - "name": "proxy" - } - ] -} -``` +| ------ | --------------- | -------------- | +| clang | 15.0.0 | -std=c++20 | +| gcc | 11.2 | -std=c++20 | +| MSVC | 19.30 | /std:c++20 | -2. Integrate `proxy` in CMakeLists.txt -``` -find_package(proxy CONFIG REQUIRED) -target_link_libraries( PRIVATE msft_proxy) -``` - -3. Run CMake with vcpkg toolchain file -``` -cmake -B -DCMAKE_TOOLCHAIN_FILE=/scripts/buildsystems/vcpkg.cmake -``` - -## Build and run tests with CMake +## Build and Run Tests with CMake ``` git clone https://github.com/microsoft/proxy.git @@ -188,6 +180,12 @@ cmake --build build -j ctest --test-dir build -j ``` +## Related Resources + +- April, 2024: [Published ISO C++ proposal P3086R2: Proxy: A Pointer-Semantics-Based Polymorphism Library](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3086r2.pdf) +- August, 2022: [proxy: Runtime Polymorphism Made Easier Than Ever](https://devblogs.microsoft.com/cppblog/proxy-runtime-polymorphism-made-easier-than-ever/) +- September, 2024: [Announcing the Proxy 3 Library for Dynamic Polymorphism](https://devblogs.microsoft.com/cppblog/announcing-the-proxy-3-library-for-dynamic-polymorphism/) + ## Contributing This project welcomes contributions and suggestions. Most contributions require you to agree to a diff --git a/docs/PRO_DEF_FREE_DISPATCH.md b/docs/PRO_DEF_FREE_DISPATCH.md new file mode 100644 index 0000000..a57614b --- /dev/null +++ b/docs/PRO_DEF_FREE_DISPATCH.md @@ -0,0 +1,68 @@ +# Macro `PRO_DEF_FREE_DISPATCH` + +```cpp +#define PRO_DEF_FREE_DISPATCH // see below +``` + +Macro `PRO_DEF_FREE_DISPATCH` defines dispatch types for free function expressions with accessibility. It supports two syntaxes: + +```cpp +// (1) +PRO_DEF_FREE_DISPATCH(dispatch_name, func_name); + +// (2) +PRO_DEF_FREE_DISPATCH(dispatch_name, func_name, accessibility_func_name); +``` + +`(1)` Equivalent to `PRO_DEF_FREE_DISPATCH(dispatch_name, func_name, func_name);` + +`(2)` Defines a class named `dispatch_name` of free function call expressions of `func_name` with accessibility. `dispatch_name` meets the [*ProAccessible*](ProAccessible.md) requirements of types `F`, `C`, and `Os...`, where `F` models concept [`facade`](facade.md), `C` is a tuple element type defined in `typename F::convention_types`, and each type `O` (possibly qualified with *cv ref noex*) in `Os...` is a tuple element type defined in `typename C::overload_types`. The functions provided by `typename dispatch_name::template accessor` are named `accessibility_func_name` and can be found by [argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl) when `accessor` is an associated class of the arguments. Let `SELF` be `std::forward(self)`, effectively equivalent to: + +```cpp +struct dispatch_name { + template + decltype(auto) operator()(T&& self, Args&&... args) + noexcept(noexcept(func_name(std::forward(self), std::forward(args)...))) + requires(requires { func_name(std::forward(self), std::forward(args)...); }) { + return func_name(std::forward(self), std::forward(args)...); + } + + template struct accessor { + accessor() = delete; + }; + template + requires(sizeof...(Os) > 1u && (std::is_trivial_v> && ...)) + struct accessor : accessor... {}; + template + struct accessor { + friend R accessibility_func_name(accessor cv ref self, Args... args) noex { + return pro::proxy_invoke(pro::access_proxy(SELF), std::forward(args)...); + } + }; +}; +``` + +## Example + +```cpp +#include +#include + +#include "proxy.h" + +PRO_DEF_FREE_DISPATCH(FreeToString, std::to_string, ToString); + +struct Stringable : pro::facade_builder + ::add_convention + ::build {}; + +int main() { + pro::proxy p = pro::make_proxy(123); + std::cout << ToString(*p) << "\n"; // Prints: "123" +} +``` + +## See Also + +- [macro `PRO_DEF_MEM_DISPATCH`](PRO_DEF_MEM_DISPATCH.md) +- [alias template `basic_facade_builder::add_convention`](basic_facade_builder/add_convention.md) diff --git a/docs/PRO_DEF_MEM_DISPATCH.md b/docs/PRO_DEF_MEM_DISPATCH.md new file mode 100644 index 0000000..c4ef01f --- /dev/null +++ b/docs/PRO_DEF_MEM_DISPATCH.md @@ -0,0 +1,73 @@ +# Macro `PRO_DEF_MEM_DISPATCH` + +```cpp +#define PRO_DEF_MEM_DISPATCH // see below +``` + +Macro `PRO_DEF_MEM_DISPATCH` defines dispatch types for member function expressions with accessibility. It supports two syntaxes: + +```cpp +// (1) +PRO_DEF_MEM_DISPATCH(dispatch_name, func_name); + +// (2) +PRO_DEF_MEM_DISPATCH(dispatch_name, func_name, accessibility_func_name); +``` + +`(1)` Equivalent to `PRO_DEF_MEM_DISPATCH(dispatch_name, func_name, func_name);` + +`(2)` Defines a class named `dispatch_name` of member function call expressions of `func_name` with accessibility. `dispatch_name` meets the [*ProAccessible*](ProAccessible.md) requirements of types `F`, `C`, and `Os...`, where `F` models concept [`facade`](facade.md), `C` is a tuple element type defined in `typename F::convention_types`, and each type `O` (possibly qualified with *cv, ref, noex*) in `Os...` is a tuple element type defined in `typename C::overload_types`. The member functions provided by `typename dispatch_name::template accessor` are named `accessibility_func_name`. Let `SELF` be `std::forward(*this)`, effectively equivalent to: + +```cpp +struct dispatch_name { + template + decltype(auto) operator()(T&& self, Args&&... args) + noexcept(noexcept(std::forward(self).func_name(std::forward(args)...))) + requires(requires { std::forward(self).func_name(std::forward(args)...); }) { + return std::forward(self).func_name(std::forward(args)...); + } + + template + struct accessor { + accessor() = delete; + }; + template + requires(sizeof...(Os) > 1u && (std::is_trivial_v> && ...)) + struct accessor : accessor... { + using accessor::accessibility_func_name ...; + }; + template + struct accessor { + R accessibility_func_name(Args... args) cv ref noex { + return pro::proxy_invoke(pro::access_proxy(SELF), std::forward(args)...); + } + }; +}; +``` + +## Example + +```cpp +#include +#include +#include + +#include "proxy.h" + +PRO_DEF_MEM_DISPATCH(MemAt, at); + +struct Dictionary : pro::facade_builder + ::add_convention + ::build {}; + +int main() { + std::vector v{"hello", "world"}; + pro::proxy p = &v; + std::cout << p->at(1) << "\n"; // Prints: "world" +} +``` + +## See Also + +- [macro `PRO_DEF_FREE_DISPATCH`](PRO_DEF_FREE_DISPATCH.md) +- [alias template `basic_facade_builder::add_convention`](basic_facade_builder/add_convention.md) diff --git a/docs/PRO_DEF_WEAK_DISPATCH.md b/docs/PRO_DEF_WEAK_DISPATCH.md new file mode 100644 index 0000000..cc16606 --- /dev/null +++ b/docs/PRO_DEF_WEAK_DISPATCH.md @@ -0,0 +1,69 @@ +# Macro `PRO_DEF_WEAK_DISPATCH` + +```cpp +#define PRO_DEF_WEAK_DISPATCH // see below +``` + +Macro `PRO_DEF_WEAK_DISPATCH` defines a "weak" dispatch type with a default implementation. It supports the following syntax: + +```cpp +PRO_DEF_WEAK_DISPATCH(dispatch_name, existing_dispatch, default_func_name); +``` + +Defines a class named `dispatch_name` that inherits `existing_dispatch` and provides additional overloads of `operator()` calling `default_func_name`. Effectively equivalent to: + +```cpp +struct dispatch_name : existing_dispatch { + using existing_dispatch::operator(); + template + decltype(auto) operator()(std::nullptr_t, Args&&... args) + noexcept(noexcept(default_func_name(std::forward(args)...))) + requires(requires { default_func_name(std::forward(args)...); }) { + return default_func_name(std::forward(args)...); + } +}; +``` + +## Notes + +A weak dispatch can extend an existing dispatch with a default implementation. This is useful when instantiating a `proxy` with a value that does not support some conventions defined by `F`. Similar with [`PRO_DEF_FREE_DISPATCH`](PRO_DEF_FREE_DISPATCH.md), `default_func_name` can be the name of an arbitrary function or anything that supports `()` syntax, including a constructor. + +## Example + +```cpp +#include +#include +#include + +#include "proxy.h" + +struct NotImplemented { + explicit NotImplemented(auto&&...) { throw std::runtime_error{ "Not implemented!" }; } + + template + operator T() const noexcept { std::terminate(); } // Or std::unreachable() in C++23 +}; + +PRO_DEF_MEM_DISPATCH(MemAt, at); +PRO_DEF_WEAK_DISPATCH(WeakMemAt, MemAt, NotImplemented); + +struct WeakDictionary : pro::facade_builder + ::add_convention + ::build {}; + +int main() { + std::vector v{"hello", "world"}; + pro::proxy p1 = &v; + std::cout << p1->at(1) << "\n"; // Prints: "world" + pro::proxy p2 = pro::make_proxy(123); + try { + p2->at(1); + } catch (const std::runtime_error& e) { + std::cout << e.what() << "\n"; // Prints: "Not implemented!" + } +} +``` + +## See Also + +- [named requirements *ProDispatch*](ProDispatch.md) diff --git a/docs/ProAccessible.md b/docs/ProAccessible.md new file mode 100644 index 0000000..f8e3ddd --- /dev/null +++ b/docs/ProAccessible.md @@ -0,0 +1,11 @@ +# Named requirements: *ProAccessible* + +Given that `F` is a type meeting the [*ProBasicFacade* requirements](ProBasicFacade.md), a type `T` meets the *ProAccessible* requirements of types `F, Args...`, if the following expressions are well-formed and have the specified semantics. + +| Expressions | Semantics | +| ------------------------------------------- | ------------------------------------------------------------ | +| `typename T::template accessor` | A type that provides accessibility to `proxy`. It shall be a trivial class type and not [final](https://en.cppreference.com/w/cpp/language/final). | + +## See Also + +- [class template `proxy`](proxy.md) diff --git a/docs/ProBasicConvention.md b/docs/ProBasicConvention.md new file mode 100644 index 0000000..06a749f --- /dev/null +++ b/docs/ProBasicConvention.md @@ -0,0 +1,14 @@ +# Named requirements: *ProBasicConvention* + +A type `C` meets the *ProBasicConvention* requirements if the following expressions are well-formed and have the specified semantics. + +| Expressions | Semantics | +| ---------------------------- | ------------------------------------------------------------ | +| `C::is_direct` | A [core constant expression](https://en.cppreference.com/w/cpp/language/constant_expression) of type `bool`, specifying whether the convention applies to a pointer type itself (`true`), or the element type of a pointer type (`false`). | +| `typename C::dispatch_type` | A [trivial type](https://en.cppreference.com/w/cpp/named_req/TrivialType) that defines how the calls are forwarded to the concrete types. | +| `typename C::overload_types` | A [tuple-like](https://en.cppreference.com/w/cpp/utility/tuple/tuple-like) type of one or more distinct types `Os`. Each type `O` in `Os` shall meet the [*ProOverload* requirements](ProOverload.md). | + +## See Also + +- [*ProBasicFacade* requirements](ProBasicFacade.md) +- [*ProConvention* requirements](ProConvention.md) diff --git a/docs/ProBasicFacade.md b/docs/ProBasicFacade.md new file mode 100644 index 0000000..c23ec74 --- /dev/null +++ b/docs/ProBasicFacade.md @@ -0,0 +1,13 @@ +# Named requirements: *ProBasicFacade* + +A type `F` meets the *ProBasicFacade* requirements if the following expressions are well-formed and have the specified semantics. + +| Expressions | Semantics | +| ------------------------------ | ------------------------------------------------------------ | +| `typename F::convention_types` | A [tuple-like](https://en.cppreference.com/w/cpp/utility/tuple/tuple-like) type that contains any number of distinct types `Cs`. Each type `C` in `Cs` shall meet the [*ProBasicConvention* requirements](ProBasicConvention.md). | +| `typename F::reflection_types` | A [tuple-like](https://en.cppreference.com/w/cpp/utility/tuple/tuple-like) type that contains any number of distinct types `Rs`. Each type `R` in `Rs` shall define reflection on pointer types. | +| `F::constraints` | A [core constant expression](https://en.cppreference.com/w/cpp/language/constant_expression) of type [`proxiable_ptr_constraints`](proxiable_ptr_constraints.md) that defines constraints to pointer types. | + +## See Also + +- [concept `facade`](facade.md) diff --git a/docs/ProConvention.md b/docs/ProConvention.md new file mode 100644 index 0000000..6573467 --- /dev/null +++ b/docs/ProConvention.md @@ -0,0 +1,11 @@ +# Named requirements: *ProConvention* + +A type `C` meets the *ProConvention* requirements of a type `P` if `C` meets the [*ProBasicConvention* requirements](ProBasicConvention.md), and the following expressions are well-formed and have the specified semantics. + +| Expressions | Semantics | +| ---------------------------- | ------------------------------------------------------------ | +| `typename C::overload_types` | A [tuple-like](https://en.cppreference.com/w/cpp/utility/tuple/tuple-like) type that contains one or more distinct types `Os`. Each type `O` in `Os` shall meet the [*ProOverload* requirements](ProOverload.md), and
- when `C::is_direct` is `true`, `typename C::dispatch_type` shall meet the [*ProDispatch* requirements](ProDispatch.md) of `P` and `O`,
- or otherwise, when `C::is_direct` is `false`, let `QP` be a qualified reference type of `P` with the *cv ref* qualifiers defined by `O` (`QP` is an lvalue reference type if `O` does not define a *ref* qualifier), `qp` be a value of `QP`, `*std::forward(qp)` shall be well-formed, and `typename C::dispatch_type` shall meet the [*ProDispatch* requirements](ProDispatch.md) of `decltype(*std::forward(qp))` and `O`. | + +## See Also + +- [*ProFacade* requirements](ProFacade.md) diff --git a/docs/ProDispatch.md b/docs/ProDispatch.md new file mode 100644 index 0000000..4774d8e --- /dev/null +++ b/docs/ProDispatch.md @@ -0,0 +1,28 @@ +# Named requirements: *ProDispatch* + +A type `D` meets the *ProDispatch* requirements of types `T` and `O` if `D` is a [trivial type](https://en.cppreference.com/w/cpp/named_req/TrivialType), `O` meets the [*ProOverload* requirelemts](ProOverload.md), and the following expressions are well-formed and have the specified semantics (let `R` be return type of `O`, `Args...` be the argument types of `O`. `args...` denotes values of type `Args...`, `v` denotes a value of type `T`, `cv` denotes a value of type `const T`). + +| Definitions of `O` | Expressions | Semantics | +| ----------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | +| `R(Args...)` | [`INVOKE`](https://en.cppreference.com/w/cpp/utility/functional)`(D{}, v, std::forward(args)...)` | Invokes dispatch type `D` with an lvalue reference of type `T` and `args...`, may throw. | +| `R(Args...) noexcept` | [`INVOKE`](https://en.cppreference.com/w/cpp/utility/functional)`(D{}, v, std::forward(args)...)` | Invokes dispatch type `D` with an lvalue reference of type `T` and `args...`, shall not throw. | +| `R(Args...) &` | [`INVOKE`](https://en.cppreference.com/w/cpp/utility/functional)`(D{}, v, std::forward(args)...)` | Invokes dispatch type `D` with an lvalue reference of type `T` and `args...`, may throw. | +| `R(Args...) & noexcept` | [`INVOKE`](https://en.cppreference.com/w/cpp/utility/functional)`(D{}, v, std::forward(args)...)` | Invokes dispatch type `D` with an lvalue reference of type `T` and `args...`, shall not throw. | +| `R(Args...) &&` | [`INVOKE`](https://en.cppreference.com/w/cpp/utility/functional)`(D{}, std::move(v), std::forward(args)...)` | Invokes dispatch type `D` with a rvalue reference of type `T` and `args...`, may throw. | +| `R(Args...) && noexcept` | [`INVOKE`](https://en.cppreference.com/w/cpp/utility/functional)`(D{}, std::move(v), std::forward(args)...)` | Invokes dispatch type `D` with a rvalue reference of type `T` and `args...`, shall not throw. | +| `R(Args...) const` | [`INVOKE`](https://en.cppreference.com/w/cpp/utility/functional)`(D{}, cv, std::forward(args)...)` | Invokes dispatch type `D` with a const reference of type `T` and `args...`, may throw. | +| `R(Args...) const noexcept` | [`INVOKE`](https://en.cppreference.com/w/cpp/utility/functional)`(D{}, cv, std::forward(args)...)` | Invokes dispatch type `D` with a const reference of type `T` and `args...`, shall not throw. | +| `R(Args...) cosnt&` | [`INVOKE`](https://en.cppreference.com/w/cpp/utility/functional)`(D{}, cv, std::forward(args)...)`, or
`d(nullptr, std::forward(args)...)` | Invokes dispatch type `D` with a const reference of type `T` and `args...`, may throw. | +| `R(Args...) const& noexcept` | [`INVOKE`](https://en.cppreference.com/w/cpp/utility/functional)`(D{}, cv, std::forward(args)...)` | Invokes dispatch type `D` with a const reference of type `T` and `args...`, shall not throw. | +| `R(Args...) const&&` | [`INVOKE`](https://en.cppreference.com/w/cpp/utility/functional)`(D{}, std::move(cv), std::forward(args)...)` | Invokes dispatch type `D` with a const rvalue reference of type `T` and `args...`, may throw. | +| `R(Args...) const&& noexcept` | [`INVOKE`](https://en.cppreference.com/w/cpp/utility/functional)`(D{}, std::move(cv), std::forward(args)...)` | Invokes dispatch type `D` with a const rvalue reference of type `T` and `args...`, shall not throw. | + +Or, + +| Definitions of `O` | Expressions | Semantics | +| -------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | +| `R(Args...)` *cv ref noex* | [`INVOKE`](https://en.cppreference.com/w/cpp/utility/functional)`(D{}, nullptr, std::forward(args)...)` | Invokes the dispatch type `D` with `nullptr` and `args...`, may or may not throw depending on `noex`. | + +## See Also + +- [*ProConvention* requirements](ProConvention.md) diff --git a/docs/ProFacade.md b/docs/ProFacade.md new file mode 100644 index 0000000..e087bb7 --- /dev/null +++ b/docs/ProFacade.md @@ -0,0 +1,13 @@ +# Named requirements: *ProFacade* + +A type `F` meets the *ProFacade* requirements of a type `P` if `F` meets the [*ProBasicFacade* requirements](ProBasicFacade.md), and `P` meets the requirements defined by [`F::constraints`](proxiable_ptr_constraints.md), and the following expressions are well-formed and have the specified semantics. + +| Expressions | Semantics | +| ------------------------------ | ------------------------------------------------------------ | +| `typename F::convention_types` | A [tuple-like](https://en.cppreference.com/w/cpp/utility/tuple/tuple-like) type that contains any number of distinct types `Cs`. Each type `C` in `Cs` shall meet the [*ProConvention* requirements](ProConvention.md) of `P`. | +| `typename F::reflection_types` | A [tuple-like](https://en.cppreference.com/w/cpp/utility/tuple/tuple-like) type that contains any number of distinct types `Rs`. Each type `R` in `Rs` shall meet the [*ProReflection* requirements](ProReflection.md) of `P`. | + +## See Also + +- [concept `proxiable`](proxiable.md) +- [*ProBasicFacade* requirements](ProBasicFacade.md) diff --git a/docs/ProOverload.md b/docs/ProOverload.md new file mode 100644 index 0000000..ca01562 --- /dev/null +++ b/docs/ProOverload.md @@ -0,0 +1,23 @@ +# Named requirements: *ProOverload* + +A type `O` meets the *ProOverload* requirements if it matches one of the following definitions, where `R` is the *return type*, `Args...` are the *argument types*. + +| Definitions of `O` | +| ----------------------------- | +| `R(Args...)` | +| `R(Args...) noexcept` | +| `R(Args...) &` | +| `R(Args...) & noexcept` | +| `R(Args...) &&` | +| `R(Args...) && noexcept` | +| `R(Args...) const` | +| `R(Args...) const noexcept` | +| `R(Args...) cosnt&` | +| `R(Args...) const& noexcept` | +| `R(Args...) const&&` | +| `R(Args...) const&& noexcept` | + +## See Also + +- [*ProConvention* requirements](ProConvention.md) +- [class template `std::move_only_function`](https://en.cppreference.com/w/cpp/utility/functional/move_only_function) diff --git a/docs/ProReflection.md b/docs/ProReflection.md new file mode 100644 index 0000000..b8f0af6 --- /dev/null +++ b/docs/ProReflection.md @@ -0,0 +1,11 @@ +# Named requirements: *ProReflection* + +A type `R` meets the *ProReflection* requirements of a type `P` if the following expressions are well-formed and have the specified semantics. + +| Expressions | Semantics | +| -------------------------- | ------------------------------------------------------------ | +| `R{std::in_place_type

}` | A [core constant expression](https://en.cppreference.com/w/cpp/language/constant_expression) that constructs a value of type `R`, reflecting implementation-defined metadata of type `P`. | + +## See Also + +[*ProFacade* requirements](ProFacade.md) diff --git a/docs/__msft_lib_proxy.md b/docs/__msft_lib_proxy.md new file mode 100644 index 0000000..aa12eae --- /dev/null +++ b/docs/__msft_lib_proxy.md @@ -0,0 +1,27 @@ +# Feature test macro `__msft_lib_proxy` + +```cpp +#define __msft_lib_proxy /* see below */ +``` + +Similar to the standard [feature test macros](https://en.cppreference.com/w/cpp/feature_test), library "Proxy" has defined a feature test macro since 3.0.0. The table below maps each release version number to the corresponding value of `__msft_lib_proxy`. + +| Version | Value of `__msft_lib_proxy` | +| ------- | --------------------------- | +| 3.0.0 | `202408L` | + +## Example + +```cpp +#include + +#include "proxy.h" + +int main() { +#if defined(__msft_lib_proxy) && __msft_lib_proxy >= 202408L + puts("Compiled with library Proxy 3.0.0 or above."); +#else + puts("Cannot determine the version of library Proxy."); +#endif +} +``` diff --git a/docs/access_proxy.md b/docs/access_proxy.md new file mode 100644 index 0000000..3342593 --- /dev/null +++ b/docs/access_proxy.md @@ -0,0 +1,66 @@ +# Function template `access_proxy` + +```cpp +template +proxy& access_proxy(A& a) noexcept; + +template +const proxy& access_proxy(const A& a) noexcept; + +template +proxy&& access_proxy(A&& a) noexcept; + +template +const proxy&& access_proxy(const A&& a) noexcept; +``` + +Accesses a `proxy` object from an [accessor](ProAccessible.md) instantiated from the `proxy`. `F` shall model concept [`facade`](facade.md). As per `facade`, `typename F::convention_types` shall be a [tuple-like](https://en.cppreference.com/w/cpp/utility/tuple/tuple-like) type containing distinct types `Cs`. There shall be a type `C` in `Cs` where `A` is the same type as `typename C::template accessor`. The behavior is undefined if `a` is not instantiated from a `proxy`. + +## Return Value + +A reference to the `proxy` that instantiates `a`. + +## Notes + +Similar to [`proxy_invoke`](proxy_invoke.md), this function can be used to implement the accessibility of `proxy`. If the facade type `F` is defined with the recommended facilities, it has full accessibility support. Specifically, when: + +- the underlying dispatch type `typename C::dispatch_type` is defined via [macro `PRO_DEF_MEM_DISPATCH`](PRO_DEF_MEM_DISPATCH.md), [macro `PRO_DEF_FREE_DISPATCH`](PRO_DEF_FREE_DISPATCH.md), or is a specialization of either [`operator_dispatch`](operator_dispatch.md) or [`conversion_dispatch`](conversion_dispatch.md), and +- the convention is defined via [`facade_builder`](basic_facade_builder.md). + +## Example + +```cpp +#include +#include + +#include "proxy.h" + +PRO_DEF_FREE_DISPATCH(FreeToString, std::to_string, ToString); + +struct Stringable : pro::facade_builder + ::add_convention + ::build {}; + +int main() { + pro::proxy p = pro::make_proxy(123); + + // Invokes with accessibility API + std::cout << ToString(*p) << "\n"; // Prints: "123" + + // How it works behind the scenes + using Convention = std::tuple_element_t<0u, Stringable::convention_types>; + using Accessor = Convention::accessor; + static_assert(std::is_base_of_v>); + Accessor& a = static_cast(*p); + pro::proxy& p2 = pro::access_proxy(a); + std::cout << std::boolalpha << (&p == &p2) << "\n"; // Prints: "true" because access_proxy converts + // an accessor back to the original proxy + auto result = pro::proxy_invoke(p2); + std::cout << result << "\n"; // Prints: "123" +} +``` + +## See Also + +- [named requirements *ProAccessible*](ProAccessible.md) +- [function template `proxy_invoke`](proxy_invoke.md) diff --git a/docs/allocate_proxy.md b/docs/allocate_proxy.md new file mode 100644 index 0000000..ec38e76 --- /dev/null +++ b/docs/allocate_proxy.md @@ -0,0 +1,55 @@ +# Function template `allocate_proxy` + +The definition of `allocate_proxy` makes use of an exposition-only class template *allocated-ptr*. An object of type `allocated-ptr` allocates the storage for another object of type `T` with an allocator of type `Alloc` and manages the lifetime of this contained object. Similar with [`std::optional`](https://en.cppreference.com/w/cpp/utility/optional), `allocated-ptr` provides `operator*` for accessing the contained object with the same qualifiers, but does not necessarily support the state where the contained object is absent. + +```cpp +// (1) +template +proxy allocate_proxy(const Alloc& alloc, Args&&... args); // freestanding-deleted + +// (2) +template +proxy allocate_proxy(const Alloc& alloc, std::initializer_list il, Args&&... args); // freestanding-deleted + +// (3) +template +proxy allocate_proxy(const Alloc& alloc, T&& value); // freestanding-deleted +``` + +`(1)` Creates a `proxy` object containing an `allocated-ptr` direct-non-list-initialized with `std::forward(args)...`. + +`(2)` Creates a `proxy` object containing an `allocated-ptr` direct-non-list-initialized with `il, std::forward(args)...`. + +`(3)` Creates a `proxy` object containing an `allocated-ptr, Alloc>` direct-non-list-initialized with `std::forward(value)`. + +## Return Value + +The constructed `proxy` object. + +## Exceptions + +Throws any exception thrown by allocation or the constructor of `T`. + +## Example + +```cpp +#include + +#include "proxy.h" + +// By default, the maximum pointer size defined by pro::facade_builder +// is 2 * sizeof(void*). This value can be overridden by `restrict_layout`. +struct Any : pro::facade_builder::build {}; + +int main() { + // sizeof(std::array) is usually greater than 2 * sizeof(void*), + // calling allocate_proxy has no limitation to the size and alignment of the target + using Target = std::array; + pro::proxy p1 = pro::allocate_proxy(std::allocator{}); +} +``` + +## See Also + +- [function template `make_proxy`](make_proxy.md) +- [named requirements *Allocator*](https://en.cppreference.com/w/cpp/named_req/Allocator) diff --git a/docs/basic_facade_builder.md b/docs/basic_facade_builder.md new file mode 100644 index 0000000..5bbd1fd --- /dev/null +++ b/docs/basic_facade_builder.md @@ -0,0 +1,95 @@ +# Class template `basic_facade_builder` + +The definitions of `basic_facade_builder` and `facade_builder` make use of the following exposition-only constants: + +```cpp +constexpr std::size_t default-size = std::numeric_limits::max(); // exposition only +constexpr constraint_level default-cl = static_cast( + std::numeric_limits>::min()); // exposition only +``` + +*default-size* and *default-cl* denote that a field in [`proxiable_ptr_constraints`](proxiable_ptr_constraints.md) is not specified in the template parameters of a `basic_facade_builder` specialization. In an instantiation of `proxiable_ptr_constraints`, any meaningful value of `max_size` and `max_align` is less than *default-size*; any meaningful value of `copyability`, `relocatability`, and `destructibility` is greater than *default-cl*. + +```cpp +template +class basic_facade_builder; + +using facade_builder = basic_facade_builder, std::tuple<>, + proxiable_ptr_constraints{ + .max_size = default-size, + .max_align = default-size, + .copyability = default-cl, + .relocatability = default-cl, + .destructibility = default-cl}>; +``` + +`class Cs`, `class Rs`, and `proxiable_ptr_constraints C` are the template parameters of `basic_facade_builder`. `basic_facade_builder` provides a member type `build` that compiles the template parameters into a [`facade`](facade.md) type. The template parameters can be modified via various member alias templates that specify `basic_facade_builder` with the modified template parameters. + +## Member Types + +| Name | Description | +| ---------------------------------------- | ------------------------------------------------------------ | +| [`build`](basic_facade_builder/build.md) | Specifies a [`facade`](facade.md) type deduced from the template parameters of the `basic_facade_builder` | + +## Member Alias Templates + +| Name | Description | +| ------------------------------------------------------------ | ------------------------------------------------------------ | +| [`add_convention`
`add_indirect_convention`
`add_direct_convention`](basic_facade_builder/add_convention.md) | Adds a convention to the template parameters | +| [`add_reflection`](basic_facade_builder/add_reflection.md) | Adds a reflection to the template parameters | +| [`add_facade`](basic_facade_builder/add_facade.md) | Adds a facade to the template parameters | +| [`restrict_layout`](basic_facade_builder/restrict_layout.md) | Specifies maximum `max_size` and `max_align` of `C` in the template parameters | +| [`support_copy`](basic_facade_builder/support_copy.md) | Specifies minimum `copyability` of `C` in the template parameters | +| [`support_relocation`](basic_facade_builder/support_relocation.md) | Specifies minimum `relocatability` of `C` in the template parameters | +| [`support_destruction`](basic_facade_builder/support_destruction.md) | Specifies minimum `destructibility` of `C` in the template parameters | + +## Member Functions + +| Name | Description | +| ----------------------- | ----------------------------------------- | +| (constructor) [deleted] | Has neither default nor copy constructors | + +## Example + +```cpp +#include + +#include "proxy.h" + +template +struct MovableCallable : pro::facade_builder + ::add_convention, Overloads...> + ::build {}; + +template +struct CopyableCallable : pro::facade_builder + ::support_copy + ::add_facade> + ::build {}; + +// MyFunction has similar functionality as std::function but supports multiple overloads +// MyMoveOnlyFunction has similar functionality as std::move_only_function but supports multiple overloads +template +using MyFunction = pro::proxy>; +template +using MyMoveOnlyFunction = pro::proxy>; + +int main() { + auto f = [](auto&&... v) { + std::cout << "f() called. Args: "; + ((std::cout << v << ":" << typeid(decltype(v)).name() << ", "), ...); + std::cout << "\n"; + }; + MyFunction p0{&f}; + (*p0)(123); // Prints "f() called. Args: 123:i," (assuming GCC) + MyMoveOnlyFunction p1{&f}; + (*p1)(); // Prints "f() called. Args:" + (*p1)(456); // Prints "f() called. Args: 456:i," + (*p1)(1.2); // Prints "f() called. Args: 1.2:d," +} +``` + +## See Also + +- [concept `facade`](facade.md) +- [macro `PRO_DEF_MEM_DISPATCH`](PRO_DEF_MEM_DISPATCH.md) diff --git a/docs/basic_facade_builder/add_convention.md b/docs/basic_facade_builder/add_convention.md new file mode 100644 index 0000000..c8f7dee --- /dev/null +++ b/docs/basic_facade_builder/add_convention.md @@ -0,0 +1,73 @@ +# `basic_facade_builder::add_convention`, `basic_facade_builder::add_direct_convention`, `basic_facade_builder::add_indirect_convention` + +```cpp +template requires(/* see below */) +using add_convention = add_indirect_convention; + +template requires(/* see below */) +using add_indirect_convention = basic_facade_builder; + +template requires(/* see below */) +using add_direct_convention = basic_facade_builder; +``` + +The alias templates `add_convention`, `add_indirect_convention`, and `add_direct_convention` of `basic_facade_builder` add convention types to the template parameters. The expression inside `requires` is equivalent to `sizeof...(Os) > 0u` and each type in `Os` meets the [*ProOverload* requirements](../ProOverload.md). Let `F` be a facade type, + +- `add_convention` is equivalent to `add_indirect_convention`. +- `add_indirect_convention` merges an implementation-defined convention type `IC` into Cs`, where: + - `IC::is_direct` is `false`. + - `typename IC::dispatch_type` is `D`. + - `typename IC::overload_types` is a [tuple-like](https://en.cppreference.com/w/cpp/utility/tuple/tuple-like) type of distinct types in `Os`. + - `typename IC::template accessor` is `typename D::template accessor`. +- `add_direct_convention` merges an implementation-defined convention type `IC` into `Cs`, where: + - `IC::is_direct` is `true`. + - `typename IC::dispatch_type` is `D`. + - `typename IC::overload_types` is a [tuple-like](https://en.cppreference.com/w/cpp/utility/tuple/tuple-like) type of distinct types in `Os`. + - `typename IC::template accessor` is `typename D::template accessor`. + +When `Cs` already contains a convention type `IC2` where `IC2::is_direct == IC::is_direct && std::is_same_v` is `true`, `Os` merges with `typename IC2::overload_types` and removes duplicates, and `std::tuple_size_v` shall not change. + +## Notes + +Adding duplicated combinations of some dispatch type and overload type is well-defined (either directly via `add_convention`, `add_indirect_convention`, `add_direct_convention`, or indirectly via [`add_facade`](add_facade.md)), and does not have side-effects to [`build`](build.md) at either compile-time or runtime. + +## Example + +```cpp +#include +#include +#include + +#include "proxy.h" + +PRO_DEF_FREE_DISPATCH(FreeToString, std::to_string, ToString); + +struct BasicStringable : pro::facade_builder + ::add_convention + ::build {}; + +struct Stringable : pro::facade_builder + ::add_facade + ::support_copy + ::add_direct_convention>, + pro::proxy() &&> + ::build {}; + +int main() { + pro::proxy p1 = std::make_shared(123); + pro::proxy p2 = p1; + pro::proxy p3 = static_cast>(std::move(p2)); + pro::proxy p4 = std::move(p3); + // pro::proxy p5 = p4; // Won't compile + std::cout << ToString(*p4) << "\n"; // Prints: "123" + std::cout << std::boolalpha << p3.has_value() << "\n"; // Prints: "false" +} +``` + +## See Also + +- [macro `PRO_DEF_MEM_DISPATCH`](../PRO_DEF_MEM_DISPATCH.md) +- [macro `PRO_DEF_FREE_DISPATCH`](../PRO_DEF_FREE_DISPATCH.md) +- [class template `operator_dispatch`](../operator_dispatch.md) +- [class template `conversion_dispatch`](../conversion_dispatch.md) +- [macro `PRO_DEF_WEAK_DISPATCH`](../PRO_DEF_WEAK_DISPATCH.md) diff --git a/docs/basic_facade_builder/add_facade.md b/docs/basic_facade_builder/add_facade.md new file mode 100644 index 0000000..0fa1281 --- /dev/null +++ b/docs/basic_facade_builder/add_facade.md @@ -0,0 +1,69 @@ +# `basic_facade_builder::add_facade` + +```cpp +template +using add_facade = basic_facade_builder; +``` + +The alias template `add_facade` of `basic_facade_builder` adds a [facade](../facade.md) type into the template parameters. It merges `typename F::convention_types` into `Cs`, `typename F::reflection_types` into `Rs`, and `F::constraints` into `C`. Optionally, it adds a convention for implicit upward conversion into `Cs` when `WithUpwardConversion` is `true`. + +## Notes + +Adding a facade type that contains duplicated convention or reflection types already defined in `Cs` or `Rs` is well-defined and does not have side effects on [`build`](build.md) at either compile-time or runtime. By default, `WithUpwardConversion` is `false`, which guarantees minimal binary size in code generation. However, upward conversion is helpful when an API requires backward compatibility. Users can opt-in to this feature by specifying `true` as the second parameter of `add_facade`, at the cost of potentially a slightly larger binary size. + +## Example + +```cpp +#include +#include + +#include "proxy.h" + +PRO_DEF_MEM_DISPATCH(MemSize, size); +PRO_DEF_MEM_DISPATCH(MemAt, at); +PRO_DEF_MEM_DISPATCH(MemEmplace, emplace); + +struct Copyable : pro::facade_builder + ::support_copy + ::build {}; + +struct BasicContainer : pro::facade_builder + ::add_convention + ::build {}; + +struct StringDictionary : pro::facade_builder + ::add_facade + ::add_facade + ::add_convention + ::build {}; + +struct MutableStringDictionary : pro::facade_builder + ::add_facade + ::add_convention + ::build {}; + +int main() { + pro::proxy p1 = + pro::make_proxy>(); + std::cout << p1->size() << "\n"; // Prints "0" + try { + std::cout << p1->at(123) << "\n"; // No output because the expression throws + } catch (const std::out_of_range& e) { + std::cerr << e.what() << "\n"; // Prints error message + } + p1->emplace(123, "lalala"); + auto p2 = p1; // Performs a deep copy + p2->emplace(456, "trivial"); + pro::proxy p3 = std::move(p2); // Performs an upward conversion from an rvalue reference + std::cout << p1->size() << "\n"; // Prints "1" + std::cout << p1->at(123) << "\n"; // Prints "lalala" + std::cout << std::boolalpha << p2.has_value() << "\n"; // Prints "false" because it is moved + std::cout << p3->size() << "\n"; // Prints "2" + std::cout << p3->at(123) << "\n"; // Prints "lalala" + std::cout << p3->at(456) << "\n"; // Prints "trivial" +} +``` + +## See Also + +- [`build`](build.md) diff --git a/docs/basic_facade_builder/add_reflection.md b/docs/basic_facade_builder/add_reflection.md new file mode 100644 index 0000000..9ac03a5 --- /dev/null +++ b/docs/basic_facade_builder/add_reflection.md @@ -0,0 +1,57 @@ +# `basic_facade_builder::add_reflection` + +```cpp +template +using add_reflection = basic_facade_builder; +``` + +The alias template `add_reflection` of `basic_facade_builder` incorporates reflection types (see [named requirements: *ProReflection*](../ProReflection.md)) into the template parameters. It merges `R` into `Rs` if `Rs` does not already contain `R`. + +## Notes + +Adding duplicate reflection types is well-defined, whether done directly via `add_reflection` or indirectly via [`add_facade`](add_facade.md). This process does not affect the behavior of [`build`](build.md) at either compile-time or runtime. + +## Example + +```cpp +#include +#include + +#include "proxy.h" + +class DebugReflection { + public: + template + constexpr explicit DebugReflection(std::in_place_type_t

) + : pointer_type_(typeid(P)), + element_type_(typeid(typename std::pointer_traits

::element_type)) {} + + void PrintDebugInfo() const { + std::cout << "Pointer type: " << pointer_type_.name() << "\n"; + std::cout << "Element type: " << element_type_.name() << "\n"; + } + + private: + const std::type_info& pointer_type_; + const std::type_info& element_type_; +}; + +struct TestFacade : pro::facade_builder + ::add_reflection + ::build {}; + +int main() { + pro::proxy p1 = std::make_shared(123); + pro::proxy_reflect(p1).PrintDebugInfo(); // Prints: "Pointer type: St10shared_ptrIiE" + // "Element type: i" (assuming GCC) + + double v = 3.14; + pro::proxy p2 = &v; + pro::proxy_reflect(p2).PrintDebugInfo(); // Prints: "Pointer type: Pd" + // "Element type: d" (assuming GCC) +} +``` + +## See Also + +- [named requirements: *ProReflection*](../ProReflection.md) diff --git a/docs/basic_facade_builder/build.md b/docs/basic_facade_builder/build.md new file mode 100644 index 0000000..39d5df3 --- /dev/null +++ b/docs/basic_facade_builder/build.md @@ -0,0 +1,93 @@ +# `basic_facade_builder::build` + +```cpp +using build = /* see below */; +``` + +Specifies a [facade](facade.md) type deduced from the template parameters of `basic_facade_builder`. Specifically, + +- `typename build::convention_types` is defined as `Cs`, and +- `typename build::reflection_types` is defined as `Rs`, and +- `build::constraints` is a [core constant expression](https://en.cppreference.com/w/cpp/language/constant_expression) of type [`proxiable_ptr_constraints`](../proxiable_ptr_constraints.md) that defines constraints to the pointer types, and +- `build::constraints.max_size` is `C::max_size` if defined by [`restrict_layout`](restrict_layout.md), otherwise `sizeof(void*) * 2u` when `C::max_size` is *default-size*, and +- `build::constraints.max_align` is `C::max_align` if defined by [`restrict_layout`](restrict_layout.md), otherwise `alignof(void*)` when `C::max_align` is *default-size*, and +- `build::constraints.copyability` is `C::copyability` if defined by [`support_copy`](support_copy.md), otherwise `constraint_level::none` when `C::copyability` is *default-cl*, and +- `build::constraints.relocatability` is `C::relocatability` if defined by [`support_rellocation`](support_relocation.md), otherwise `constraint_level::nothrow` when `C::relocatability` is *default-cl*, and +- `build::constraints.destructibility` is `C::destructibility` if defined by [`support_destruction`](support_destruction.md), otherwise `constraint_level::nothrow` when `C::destructibility` is *default-cl*. + +The definition of type `build` makes use of the following exposition-only function: + +```cpp +consteval proxiable_ptr_constraints normalize(proxiable_ptr_constraints value) { + if (value.max_size == default-size) + { value.max_size = sizeof(void*) * 2u; } + if (value.max_align == default-size) + { value.max_align = alignof(void*); } + if (value.copyability == default-cl) + { value.copyability = constraint_level::none; } + if (value.relocatability == default-cl) + { value.relocatability = constraint_level::nothrow; } + if (value.destructibility == default-cl) + { value.destructibility = constraint_level::nothrow; } + return value; +} +``` + +## Member Types + +| Name | Definition | +| ------------------ | ---------- | +| `convention_types` | `Cs` | +| `reflection_types` | `Rs` | + +## Member Constants + +| Name | Definition | +| ---------------------------------- | -------------- | +| `constraints` [static] [constexpr] | `normalize(C)` | + +## Notes + +It is encouraged to inherit `build` with an empty `struct` before specifying a [`proxy`](../proxy.md), rather than `using` or `typedef` the `build` type into an alias, to improve compilation performance. + +The default values of the fields of [`proxiable_ptr_constraints`](../proxiable_ptr_constraints.md) are based on our engineering practices. The default values of `max_size` and `max_alignment` are usually sufficient for many implementations of [fancy pointers](https://en.cppreference.com/w/cpp/named_req/Allocator#Fancy_pointers), such as [`std::unique_ptr`](https://en.cppreference.com/w/cpp/memory/unique_ptr), [`std::shared_ptr`](https://en.cppreference.com/w/cpp/memory/shared_ptr), and [boost::interprocess::offset_ptr](https://www.boost.org/doc/libs/1_85_0/doc/html/interprocess/offset_ptr.html). A larger combination of size and alignment ensures better compatibility with the implementation of the underlying pointers and reduces heap allocation when the element type fits in the buffer (see [function template `make_proxy`](../make_proxy.md)), at the cost of making the corresponding [`proxy`](../proxy.md) objects larger. + +## Example + +```cpp +#include + +#include "proxy.h" + +struct DefaultBase : pro::facade_builder + ::build {}; + +struct CopyableBase : pro::facade_builder + ::support_copy + ::build {}; + +struct TrivialBase : pro::facade_builder + ::support_copy + ::support_relocation + ::support_destruction + ::restrict_layout + ::build {}; + +int main() { + static_assert(!std::is_copy_constructible_v>); + static_assert(std::is_nothrow_move_constructible_v>); + static_assert(std::is_nothrow_destructible_v>); + + static_assert(std::is_copy_constructible_v>); + static_assert(std::is_nothrow_move_constructible_v>); + static_assert(std::is_nothrow_destructible_v>); + + static_assert(std::is_trivially_copy_constructible_v>); + static_assert(std::is_trivially_move_constructible_v>); + static_assert(std::is_trivially_destructible_v>); +} +``` + +## See Also + +- [class template `proxy`](../proxy.md) diff --git a/docs/basic_facade_builder/restrict_layout.md b/docs/basic_facade_builder/restrict_layout.md new file mode 100644 index 0000000..3d78fb6 --- /dev/null +++ b/docs/basic_facade_builder/restrict_layout.md @@ -0,0 +1,47 @@ +# `basic_facade_builder::restrict_layout` + +```cpp +template + requires(std::has_single_bit(PtrAlign) && PtrSize % PtrAlign == 0u) +using restrict_layout = basic_facade_builder; +``` + +The alias template `restrict_layout` of `basic_facade_builder` adds layout restrictions to the template parameters, specifically `C::max_size` and `C::max_align`. The default value of `PtrAlign` is the maximum possible alignment of an object of size `PtrSize`, not greater than `alignof(std::max_align_t`). After applying the restriction, `C::max_size` becomes `std::min(C::max_size, PtrSize)`, and `C::max_align` becomes `std::min(C::max_align, PtrAlign)`. + +## Notes + +If no layout restriction is applied before specifying [`build`](build.md), the default value of `build::constraints.max_size` is `sizeof(void*) * 2`, and the default value of `build::constraints.max_align` is `alignof(void*)`. + +## Example + +```cpp +#include +#include + +#include "proxy.h" + +struct DefaultFacade : pro::facade_builder::build {}; + +struct SmallFacade : pro::facade_builder + ::restrict_layout + ::build {}; + +int main() { + static_assert(sizeof(pro::proxy) > sizeof(pro::proxy)); + static_assert(pro::proxiable, DefaultFacade>); + static_assert(pro::proxiable, SmallFacade>); + static_assert(pro::proxiable, DefaultFacade>); + static_assert(!pro::proxiable, SmallFacade>); + static_assert(pro::inplace_proxiable_target, DefaultFacade>); + static_assert(!pro::inplace_proxiable_target, SmallFacade>); + static_assert(!pro::inplace_proxiable_target, DefaultFacade>); + static_assert(!pro::inplace_proxiable_target, SmallFacade>); + pro::proxy p1 = std::make_shared(123); + // pro::proxy p2 = std::make_shared(123); // Won't compile +} +``` + +## See Also + +- [`build`](build.md) +- [concept `inplace_proxiable_target`](../inplace_proxiable_target.md) diff --git a/docs/basic_facade_builder/support_copy.md b/docs/basic_facade_builder/support_copy.md new file mode 100644 index 0000000..79a7d50 --- /dev/null +++ b/docs/basic_facade_builder/support_copy.md @@ -0,0 +1,38 @@ +# `basic_facade_builder::support_copy` + +```cpp +template +using support_copy = basic_facade_builder; +``` + +The alias template `support_copy` of `basic_facade_builder` adds copyability support to the template parameters, specifically `C::copyability`. After the operation, `C::copyability` becomes `std::max(C::copyability, CL)`. + +## Notes + +If no copyability support is applied before specifying [`build`](build.md), the default value of `build::constraints.copyability` is `pro::constraint_level::none`. + +## Example + +```cpp +#include + +#include "proxy.h" + +struct Movable : pro::facade_builder::build {}; + +struct Copyable : pro::facade_builder + ::support_copy + ::build {}; + +int main() { + pro::proxy p1 = std::make_unique(123); + // pro::proxy p2 = std::make_unique(123); // Won't compile + pro::proxy p3 = std::make_shared(456); + // auto p4 = p1; // Won't compile + auto p5 = p3; +} +``` + +## See Also + +- [`support_relocation`](support_relocation.md) diff --git a/docs/basic_facade_builder/support_destruction.md b/docs/basic_facade_builder/support_destruction.md new file mode 100644 index 0000000..66f705e --- /dev/null +++ b/docs/basic_facade_builder/support_destruction.md @@ -0,0 +1,36 @@ +# `basic_facade_builder::support_destruction` + +```cpp +template +using support_destruction = basic_facade_builder; +``` + +The alias template `support_destruction` of `basic_facade_builder` adds destruction support to the template parameters, specifically `C::destructibility`. After the operation, `C::destructibility` becomes `std::max(C::destructibility, CL)`. + +## Notes + +If no destructibility support is applied before specifying [`build`](build.md), the default value of `build::constraints.destructibility` is `pro::constraint_level::nothrow`. + +## Example + +```cpp +#include + +#include "proxy.h" + +struct Movable : pro::facade_builder::build {}; + +struct NonriviallyDestructible : pro::facade_builder + ::support_relocation + ::support_destruction + ::build {}; + +int main() { + static_assert(std::is_nothrow_destructible_v>); + static_assert(!std::is_nothrow_destructible_v>); +} +``` + +## See Also + +- [`support_relocation`](support_relocation.md) diff --git a/docs/basic_facade_builder/support_relocation.md b/docs/basic_facade_builder/support_relocation.md new file mode 100644 index 0000000..a59cfad --- /dev/null +++ b/docs/basic_facade_builder/support_relocation.md @@ -0,0 +1,39 @@ +# `basic_facade_builder::support_relocation` + +```cpp +template +using support_relocation = basic_facade_builder; +``` + +The alias template `support_relocation` of `basic_facade_builder` adds relocatability support to the template parameters, specifically `C::relocatability`. After the operation, `C::relocatability` becomes `std::max(C::relocatability, CL)`. + +## Notes + +If no relocatability support is applied before specifying [`build`](build.md), the default value of `build::constraints.relocatability` is `pro::constraint_level::nothrow`. + +## Example + +```cpp +#include + +#include "proxy.h" + +struct Movable : pro::facade_builder::build {}; + +struct Trivial : pro::facade_builder + ::support_copy + ::support_relocation + ::support_destruction + ::build {}; + +int main() { + pro::proxy p1 = std::make_unique(123); + // pro::proxy p2 = std::make_unique(456); // Won't compile + double v = 3.14; + pro::proxy p3 = &v; // Compiles because double* is trivial +} +``` + +## See Also + +- [`support_copy`](support_copy.md) diff --git a/docs/constraint_level.md b/docs/constraint_level.md new file mode 100644 index 0000000..3c60d45 --- /dev/null +++ b/docs/constraint_level.md @@ -0,0 +1,20 @@ +# Enum class `constraint_level` + +```cpp +enum class constraint_level : /* unspecified */ { + none, nontrivial, nothrow, trivial +}; +``` + +`constraint_level` defines the constraints on a type's lifetime operations (construction, relocation, or destruction). For a given operation `O`: + +- `none`: No restrictions on `O`. +- `nontrivial`: `O` shall be supported. +- `nothrow`: `O` shall be supported and shall not throw. +- `trivial`: `O` shall be supported and shall be trivial. + +The specific semantics of each value depends on its context. + +## See Also + +- [class `proxiable_ptr_constraints`](proxiable_ptr_constraints.md) diff --git a/docs/conversion_dispatch.md b/docs/conversion_dispatch.md new file mode 100644 index 0000000..fb795de --- /dev/null +++ b/docs/conversion_dispatch.md @@ -0,0 +1,67 @@ +# Class template `conversion_dispatch` + +```cpp +template +class conversion_dispatch; +``` + +Class template `conversion_dispatch` is a [dispatch](ProDispatch.md) type for explicit or implicit type conversion expressions. It meets the [*ProAccessible* requirements](ProAccessible.md) of applicable types. `T` is the target type for conversion, and `Expl` specifies whether the conversion is explicit (defaults to `true`, as recommended in [C++ Core Guidelines C.164: Avoid implicit conversion operators](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c164-avoid-implicit-conversion-operators)). + +## Member Functions + +| Name | Description | +| ---------------------------------------------------- | ----------------------------------------- | +| (constructor) [trivial] | constructs a `conversion_dispatch` object | +| [`operator()`](conversion_dispatch/operator_call.md) | invokes the dispatch | + +## Member Types + +| Name | Description | +| --------------------------------------------- | --------------------------------- | +| [`accessor`](conversion_dispatch/accessor.md) | provides accessibility to `proxy` | + +## Example + +```cpp +#include +#include + +#include "proxy.h" + +struct DoubleConvertible : pro::facade_builder + ::add_convention, double() const> + ::build {}; + +struct Runnable : pro::facade_builder + ::add_convention, void()> + ::build {}; + +struct CopyableRunnable : pro::facade_builder + ::support_copy + ::add_facade + ::add_direct_convention, false>, + pro::proxy() const&, pro::proxy() &&> + ::build {}; + +int main() { + // Explicit conversion + pro::proxy p1 = pro::make_proxy(123); // p1 holds an integer + std::cout << std::fixed << std::setprecision(10) << std::boolalpha; + std::cout << static_cast(*p1) << "\n"; // Prints: "123.0000000000" + + // Implicit conversion + pro::proxy p2 = pro::make_proxy( + [] { std::cout << "Lambda expression invoked\n"; }); + auto p3 = p2; // Copy construction + pro::proxy p4 = p3; // Implicit conversion via const reference of pro::proxy + std::cout << p3.has_value() << "\n"; // Prints: "true" + // auto p5 = p4; // Won't compile because pro::proxy is not copy-constructible + pro::proxy p6 = std::move(p3); // Implicit conversion via rvalue reference of pro::proxy + std::cout << p3.has_value() << "\n"; // Prints: "false" + (*p6)(); // Prints: "Lambda expression invoked" +} +``` + +## See Also + +- [class template `operator_dispatch`](operator_dispatch.md) diff --git a/docs/conversion_dispatch/accessor.md b/docs/conversion_dispatch/accessor.md new file mode 100644 index 0000000..94246f9 --- /dev/null +++ b/docs/conversion_dispatch/accessor.md @@ -0,0 +1,30 @@ +# Class template `conversion_dispatch::accessor` + +```cpp +// (1) +template +struct accessor { + accessor() = delete; +}; + +// (2) +template + requires(sizeof...(Os) > 1u && (std::is_trivial_v> && ...)) +struct accessor : accessor... { + using accessor::operator T...; +}; + +// (3) +template +struct accessor { + explicit(Expl) operator T() cv ref noex; +}; +``` + +Let `SELF` be `std::forward(*this)`. + +`(1)` The default implementation of `accessor` is not constructible. + +`(2)` When `sizeof...(Os)` is greater than `1`, and `accessor...` are trivial types, inherits all `accessor...` types and `using` their `operator T`. + +`(3)` When `sizeof...(Os)` is `1` and the only type `O` in `Os` is `T() cv ref noex`, provides an explicit (when `Expl` is `true`) or implicit (when `Expl` is `false`) `operator T()` with the same *cv ref noex* specifiers. `accessor::operator T()` is equivalent to `return proxy_invoke(access_proxy(SELF))`. diff --git a/docs/conversion_dispatch/operator_call.md b/docs/conversion_dispatch/operator_call.md new file mode 100644 index 0000000..7ced306 --- /dev/null +++ b/docs/conversion_dispatch/operator_call.md @@ -0,0 +1,14 @@ +# `conversion_dispatch::operator()` + +```cpp +template +T operator()(U&& value) + noexcept(std::conditional_t, + std::is_nothrow_convertible>::value) + requires(std::conditional_t, + std::is_convertible>::value); +``` + +Converts `value` to type `T`. When `Expl` is `true`, `std::is_constructible` is required to be `true`, or otherwise when `Expl` is `false`, `std::is_convertible` is required to be `true`. + +Returns `static_cast(std::forward(value))`. diff --git a/docs/facade.md b/docs/facade.md new file mode 100644 index 0000000..8e4024e --- /dev/null +++ b/docs/facade.md @@ -0,0 +1,16 @@ +# Concept `facade` + +```cpp +template +concept facade = /* see-below */; +``` + +The concept `facade` specifies that a type `F` models a facade of [`proxy`](proxy.md). If `F` depends on an incomplete type, and its evaluation could yield a different result if that type were hypothetically completed, the behavior is undefined. `facade` is `true` when `F` meets the [*ProBasicFacade* requirements](ProBasicFacade.md); otherwise, it is `false`. + +Note that concept `facade` does not impose strong constraints on the dependent convention and reflection types. It is recommended to use [`facade_builder`](basic_facade_builder.md) to define a facade type that models concept `facade`. + +## See Also + +- [class template `basic_facade_builder`](basic_facade_builder.md) +- [concept `proxiable`](proxiable.md) +- [function template `make_proxy`](make_proxy.md) diff --git a/docs/faq.md b/docs/faq.md new file mode 100644 index 0000000..703f700 --- /dev/null +++ b/docs/faq.md @@ -0,0 +1,68 @@ +# Proxy's Frequently Asked Questions + +- [**What is "Proxy" and how does it work?**](#what) +- [**Why is "Proxy" so popular?**](#why-popular) +- [**Who is "Proxy" for?**](#who-for) +- [**Why should I use "Proxy"?**](#why-use) +- [**How to learn "Proxy" effectively?**](#how-learn) +- [**How to integrate "Proxy" into my project?**](#how-integrate) +- [**My existing project uses virtual functions. How should I migrate to "Proxy"?**](#how-migrate) +- [**How is the performance compared to virtual functions?**](#performance) +- [**Why is "Proxy" based on pointer semantics rather than value semantics like `std::function`?**](#why-pointer) +- [**Why does "Proxy" define several macros instead of modern C++ facilities?**](#why-macros) +- [**What is the standardization progress of this library?**](#standardization) +- [**What should I do if I found this library deficient in my scenario?**](#help-needed) + +### What is "Proxy" and how does it work? + +"Proxy" is a single-header, cross-platform C++20 template library for modern runtime polymorphism based on pointer-semantics. Similar with C++ [virtual functions](https://en.cppreference.com/w/cpp/language/virtual), it generates indirect functions behind the scenes at compile-time but does not require inheritance. It also has [GC](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science))-like behavior that allows different objects of the same `proxy` type to have different lifetime models without requiring runtime GC overhead. + +### Why is "Proxy" so popular? + +"Proxy" is built by engineers at Microsoft and initially deployed in the Windows operating system. For 40 years, the inheritance-based polymorphism paradigm has been the only scalable solution for runtime polymorphism in C++. However, a "virtual function" is no longer the optimal choice for runtime polymorphism today, and new languages with better paradigms, like [traits in Rust](https://doc.rust-lang.org/book/ch10-02-traits.html), are emerging. "Proxy" is our latest and greatest solution for generic runtime polymorphism in C++. It is easy to integrate and makes C++ feel like a brand new language when dealing with runtime abstractions. + +### Who is "Proxy" for? + +We encourage every C++ developer to use this library for production, including in embedded engineering scenarios. Before deciding to write any virtual function, consider whether using "Proxy" could simplify the architecture. + +### Why should I use "Proxy"? + +While virtual functions have served well for decades, "Proxy" offers modern solutions that can lead to better performance, easier maintenance, and more flexible code design when it comes to runtime abstraction. + +### How to learn "Proxy" effectively? + +The fundamental abstraction of "Proxy" is called "facade". It is recommended for beginners to start with the examples in the [README](../README.md), try to understand the pattern of defining a [`facade`](ProFacade.md) type, using a facade type to specify a [`proxy`](proxy.md) type, and creating and using a proxy object at runtime. Don't hesitate to consult the [specifications](specifications.md) for more details about any facility in the library. + +### How to integrate "Proxy" into my project? + +Since "Proxy" is a single-header library, you can simply navigate to the [latest release](https://github.com/microsoft/proxy/releases), download the source code, and include "proxy.h" in your project. Make sure your compiler version meets the [minimum requirements for compilers](../README.md#compiler-req). If your project has already integrated with [vcpkg](https://vcpkg.io/) or [conan](https://conan.io/), just search for the keyword "proxy" and install it. Thanks to the community that helped port "Proxy" to these platforms! + + +### My existing project uses virtual functions. How should I migrate to "Proxy"? + +Follow the 4 steps below to upgrade an existing project from using virtual functions to "Proxy": + +1. Update the compiler version to meet our [minimum requirements for compilers](../README.md#compiler-req). +2. Define [`facade`](ProFacade.md) types that match the "base classes with virtual functions" (virtual base classes). +3. Replace all the usage of virtual base classes with [`proxy`](proxy.md) from the API boundary. +4. Remove all the definitions and inheritance of virtual base classes. + +### How is the performance compared to virtual functions? + +The design of "Proxy" follows the [zero-overhead principle](https://en.cppreference.com/w/cpp/language/Zero-overhead_principle). With general compiler optimizations, "Proxy" is expected to generate high quality code in most scenarios that is not worse than an equivalent hand-written implementation with or without virtual functions. In practice, when a construct of runtime abstraction has ownership of its context, "Proxy" usually has better performance than the inheritance-based approach in lifetime management. When performing an indirect call, "Proxy" usually generates similar code to a virtual function with equal performance. We have observed that when the concrete implementation and the abstraction appear in the same translation unit, a virtual function is more likely to be devirtualized in the generated code, but sometimes the compiler won't do that trick for "Proxy". + +### Why is "Proxy" based on pointer semantics rather than value semantics like [std::function](https://en.cppreference.com/w/cpp/utility/functional/function)? + +At the beginning, we explored the feasibility of designing a general-purpose polymorphic wrapper based on value semantics, just like [`std::function`](https://en.cppreference.com/w/cpp/utility/functional/function) and [`std::move_only_function`](https://en.cppreference.com/w/cpp/utility/functional/move_only_function). It is technically feasible, but not as good as those languages with [GC](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)) like C# or Java in terms of usability. We had a hard time refining the theory of [OOP](https://en.wikipedia.org/wiki/Object-oriented_programming) and finally realized that indirection is the nature of computer science, and decided to leverage the concept of pointer in C++ as the basis of "Proxy". + +### Why does "Proxy" define several macros instead of modern C++ facilities? + +"Proxy" defines 4 macros: [`__msft_lib_proxy`](__msft_lib_proxy.md), [`PRO_DEF_MEM_DISPATCH`](PRO_DEF_MEM_DISPATCH.md), [`PRO_DEF_FREE_DISPATCH`](PRO_DEF_FREE_DISPATCH.md), and [`PRO_DEF_WEAK_DISPATCH`](PRO_DEF_WEAK_DISPATCH.md). [`__msft_lib_proxy`](__msft_lib_proxy.md) is the feature test macro, following the existing practice in the C++20 standard. The other 3 macros are fundamental facilities to define a custom [`dispatch`](ProDispatch.md) type. These macros cannot be replaced by modern C++ facilities because there is no existing language feature prior to C++26 that allows generating a function with an arbitrary name. As a result, "Proxy" does not provide a default interface for [modules](https://en.cppreference.com/w/cpp/language/modules) as of now. + +### What is the standardization progress of this library? + +Currently, there is [an ongoing proposal](https://wg21.link/p3086) being reviewed in the ISO C++ committee. The progress can be tracked [here](https://github.com/cplusplus/papers/issues/1741). + +### What should I do if I found this library deficient in my scenario? + +Please search for your scenario in the existing issues first, and feel free to file an a new one on demand, following the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). diff --git a/docs/inplace_proxiable_target.md b/docs/inplace_proxiable_target.md new file mode 100644 index 0000000..f431473 --- /dev/null +++ b/docs/inplace_proxiable_target.md @@ -0,0 +1,34 @@ +# Concept `inplace_proxiable_target` + +```cpp +template +concept inplace_proxiable_target = proxiable */, F>; +``` + +The concept `inplace_proxiable_target` specifies that a value type `T`, when wrapped by an implementation-defined non-allocating pointer type, models a contained value type of [`proxy`](proxy.md). The size and alignment of this implementation-defined pointer type are guaranteed to be equal to those of type `T`. + +## Example + +```cpp +#include + +#include "proxy.h" + +// By default, the maximum pointer size defined by pro::facade_builder +// is 2 * sizeof(void*). This value can be overridden by `restrict_layout`. +struct Any : pro::facade_builder::build {}; + +int main() { + // sizeof(int) is usually not greater than sizeof(void*) for modern + // 32/64-bit compilers + static_assert(pro::inplace_proxiable_target); + + // sizeof(std::array) is usually greater than 2 * sizeof(void*) + static_assert(!pro::inplace_proxiable_target, Any>); +} +``` + +## See Also + +- [concept `proxiable`](proxiable.md) +- [function template `make_proxy_inplacce`](make_proxy_inplace.md) diff --git a/docs/make_proxy.md b/docs/make_proxy.md new file mode 100644 index 0000000..34ad370 --- /dev/null +++ b/docs/make_proxy.md @@ -0,0 +1,74 @@ +# Function template `make_proxy` + +The definitions of `make_proxy` make use of the following exposition-only function: + +```cpp +template +proxy make-proxy-internal(Args&&... args) { + if constexpr (inplace_proxiable_target) { + return make_proxy_inplace(std::forward(args)...); + } else { + return allocate_proxy(std::allocator{}, std::forward(args)...); + } +} +``` + +```cpp +// (1) +template +proxy make_proxy(Args&&... args); // freestanding-deleted + +// (2) +template +proxy make_proxy(std::initializer_list il, Args&&... args); // freestanding-deleted + +// (3) +template +proxy make_proxy(T&& value); // freestanding-deleted +``` + +`(1)` Equivalent to `return make-proxy-internal(std::forward(args)...)`. + +`(2)` Equivalent to `return make-proxy-internal(il, std::forward(args)...)`. + +`(3)` Equivalent to `return make-proxy-internal>(std::forward(value))`. + +## Return Value + +The constructed `proxy` object. + +## Exceptions + +Throws any exception thrown by allocation and the constructor of `T`. + +## Example + +```cpp +#include +#include +#include + +#include "proxy.h" + +struct Printable : pro::facade_builder + ::add_convention, std::ostream&(std::ostream&) const> + ::build {}; + +int main() { + pro::proxy p1 = pro::make_proxy(true); // From bool + pro::proxy p2 = pro::make_proxy(123); // From int + pro::proxy p3 = pro::make_proxy(3.1415926); // From double + pro::proxy p4 = pro::make_proxy("lalala"); // From const char* + pro::proxy p5 = pro::make_proxy(5, 'x'); // From a in-place constructed string + std::cout << std::boolalpha << *p1 << "\n"; // Prints: "true" + std::cout << *p2 << "\n"; // Prints: "123" + std::cout << std::fixed << std::setprecision(10) << *p3 << "\n"; // Prints: "3.1415926000" + std::cout << *p4 << "\n"; // Prints: "lalala" + std::cout << *p5 << "\n"; // Prints: "xxxxx" +} +``` + +## See Also + +- [function template `make_proxy_inplace`](make_proxy_inplace.md) +- [function template `allocate_proxy`](allocate_proxy.md) diff --git a/docs/make_proxy_inplace.md b/docs/make_proxy_inplace.md new file mode 100644 index 0000000..4d17562 --- /dev/null +++ b/docs/make_proxy_inplace.md @@ -0,0 +1,62 @@ +# Function template `make_proxy_inplace` + +The definition of `make_proxy_inplace` makes use of an exposition-only class template *sbo-ptr*. Similar to [`std::optional`](https://en.cppreference.com/w/cpp/utility/optional), `sbo-ptr` contains the storage for an object of type `T`, manages its lifetime, and provides `operator*` for access with the same qualifiers. However, it does not necessarily support the state where the contained object is absent. `sbo-ptr` has the same size and alignment as `T`. + +```cpp +// (1) +template T, class... Args> +proxy make_proxy_inplace(Args&&... args) + noexcept(std::is_nothrow_constructible_v); + +// (2) +template T, class U, class... Args> +proxy make_proxy_inplace(std::initializer_list il, Args&&... args) + noexcept(std::is_nothrow_constructible_v< + T, std::initializer_list&, Args...>); + +// (3) +template +proxy make_proxy_inplace(T&& value) + noexcept(std::is_nothrow_constructible_v, T>) + requires(inplace_proxiable_target, F>); +``` + +`(1)` Creates a `proxy` object containing a `sbo-ptr` direct-non-list-initialized with `std::forward(args)...`. + +`(2)` Creates a `proxy` object containing a `sbo-ptr` direct-non-list-initialized with `il, std::forward(args)...`. + +`(3)` Creates a `proxy` object containing a `sbo-ptr>` direct-non-list-initialized with `std::forward(value)`. + +## Return Value + +The constructed `proxy` object. + +## Exceptions + +Throws any exception thrown by the constructor of `T`. + +## Example + +```cpp +#include + +#include "proxy.h" + +// By default, the maximum pointer size defined by pro::facade_builder +// is 2 * sizeof(void*). This value can be overridden by `restrict_layout`. +struct Any : pro::facade_builder::build {}; + +int main() { + // sizeof(int) is usually not greater than sizeof(void*) for modern + // 32/64-bit compilers + pro::proxy p1 = pro::make_proxy_inplace(123); + + // sizeof(std::array) is usually greater than 2 * sizeof(void*) + // pro::proxy p2 = pro::make_proxy_inplace>(); // Won't compile +} +``` + +## See Also + +- [concept `inplace_proxiable_target`](inplace_proxiable_target.md) +- [function template `make_proxy`](make_proxy.md) diff --git a/docs/operator_dispatch.md b/docs/operator_dispatch.md new file mode 100644 index 0000000..27aa7ac --- /dev/null +++ b/docs/operator_dispatch.md @@ -0,0 +1,143 @@ +# Class template `operator_dispatch` + +The definition of `operator_dispatch` makes use of an exposition-only type *string-literal*, which is constructible from a `char` array at compile-time and can be used as a non-type template argument. + +```cpp +template +class operator_dispatch; +``` + +Class template `operator_dispatch` is a [dispatch](ProDispatch.md) type for operator expressions. `Sign` represents the sign of operator (SOP) as a string literal (e.g., `"+"` for operator `+`). `Rhs` specifies whether the [`proxy`](proxy.md) operand is on the right-hand side of a binary operator. + +## Supported SOPs + +As per C++20 specifications, there are 44 different SOPs. `operator_dispatch` supports the following 36 SOPs: + +```text +"+", "-", "*", "/", "%", "++", "--", "==", "!=", ">", "<", ">=", "<=", "<=>", "!", "&&", "||", "~", "&", "|", "^", "<<", ">>", "+=", "-=", "*=", "/=", "&=", "|=", "^=", "<<=", ">>=", ",", "->*", "()", "[]" +``` + +The following 8 SOPs are not supported: + +- `new`, `new[]`, `delete`, `delete[]`, and `co_await` are context-dependent and not applicable for `proxy`. +- Assignment (`=`) and indirection (`->`) are ambiguous in the context of `proxy`. +- Literals (`""`) apply only to a limited number of primitive types and are not applicable for `proxy`. + +## Type Conversion + +Type conversion expressions, although using `operator` syntax, do not have a specific sign and are not within the scope of `operator_dispatch`. They are supported by another class template [`conversion_dispatch`](conversion_dispatch.md). + +## Specializations + +Let `self` be the operand of [`proxy`](proxy.md), and `other` and `others...` be the other operand(s) in the expression of an operator. `operator_dispatch` has the following specializations for various expressions: + +| Specializations | Expressions | +| ---------------------------------- | ----------------------- | +| `operator_dispatch<"+", false>` | `+self`, `self + other` | +| `operator_dispatch<"-", false>` | `-self`, `self - other` | +| `operator_dispatch<"*", false>` | `*self`, `self * other` | +| `operator_dispatch<"/", false>` | `self / other` | +| `operator_dispatch<"%", false>` | `self % other` | +| `operator_dispatch<"++", false>` | `++self`, `self++` | +| `operator_dispatch<"--", false>` | `--self`, `self--` | +| `operator_dispatch<"==", false>` | `self == other` | +| `operator_dispatch<"!=", false>` | `self != other` | +| `operator_dispatch<">", false>` | `self > other` | +| `operator_dispatch<"<", false>` | `self < other` | +| `operator_dispatch<">=", false>` | `self >= other` | +| `operator_dispatch<"<=", false>` | `self <= other` | +| `operator_dispatch<"<=>", false>` | `self <=> other` | +| `operator_dispatch<"!", false>` | `!self` | +| `operator_dispatch<"&&", false>` | `self && other` | +| `operator_dispatch<"\|\|", false>` | `self \|\| other` | +| `operator_dispatch<"~", false>` | `!self` | +| `operator_dispatch<"&", false>` | `&self`, `self & other` | +| `operator_dispatch<"\|", false>` | `self \| other` | +| `operator_dispatch<"^", false>` | `self ^ other` | +| `operator_dispatch<"<<", false>` | `self << other` | +| `operator_dispatch<">>", false>` | `self >> other` | +| `operator_dispatch<"+=", false>` | `self += other` | +| `operator_dispatch<"-=", false>` | `self -= other` | +| `operator_dispatch<"*=", false>` | `self *= other` | +| `operator_dispatch<"/=", false>` | `self /= other` | +| `operator_dispatch<"&=", false>` | `self &= other` | +| `operator_dispatch<"\|=", false>` | `self \|= other` | +| `operator_dispatch<"^=", false>` | `self ^= other` | +| `operator_dispatch<"<<=", false>` | `self <<= other` | +| `operator_dispatch<">>=", false>` | `self >>= other` | +| `operator_dispatch<",", false>` | `self, other` | +| `operator_dispatch<"->*", false>` | `self ->* other` | +| `operator_dispatch<"()", false>` | `self(others...)` | +| `operator_dispatch<"[]", false>` | `self[other]` (until C++23)
`self[others...]` (since C++23) | +| `operator_dispatch<"+", true>` | `other + self` | +| `operator_dispatch<"-", true>` | `other - self` | +| `operator_dispatch<"*", true>` | `other * self` | +| `operator_dispatch<"/", true>` | `other / self` | +| `operator_dispatch<"%", true>` | `other % self` | +| `operator_dispatch<"==", true>` | `other == self` | +| `operator_dispatch<"!=", true>` | `other != self` | +| `operator_dispatch<">", true>` | `other > self` | +| `operator_dispatch<"<", true>` | `other < self` | +| `operator_dispatch<">=", true>` | `other >= self` | +| `operator_dispatch<"<=", true>` | `other <= self` | +| `operator_dispatch<"<=>", true>` | `other <=> self` | +| `operator_dispatch<"&&", true>` | `other && self` | +| `operator_dispatch<"\|\|", true>` | `other \|\| self` | +| `operator_dispatch<"&", true>` | `other & self` | +| `operator_dispatch<"\|", true>` | `other \| self` | +| `operator_dispatch<"^", true>` | `other ^ self` | +| `operator_dispatch<"<<", true>` | `other << self` | +| `operator_dispatch<">>", true>` | `other >> self` | +| `operator_dispatch<"+=", true>` | `other += self` | +| `operator_dispatch<"-=", true>` | `other -= self` | +| `operator_dispatch<"*=", true>` | `other *= self` | +| `operator_dispatch<"/=", true>` | `other /= self` | +| `operator_dispatch<"&=", true>` | `other &= self` | +| `operator_dispatch<"\|=", true>` | `other \|= self` | +| `operator_dispatch<"^=", true>` | `other ^= self` | +| `operator_dispatch<"<<=", true>` | `other <<= self` | +| `operator_dispatch<">>=", true>` | `other >>= self` | +| `operator_dispatch<",", true>` | `other, self` | +| `operator_dispatch<"->*", true>` | `other ->* self` | + +## Member Functions + +| Name | Description | +| -------------------------------------------------- | ---------------------------------------- | +| (constructor) [trivial] | constructs an `operator_dispatch` object | +| [`operator()`](operator_dispatch/operator_call.md) | invokes the dispatch | + +## Member Types + +| Name | Description | +| ------------------------------------------- | --------------------------------- | +| [`accessor`](operator_dispatch/accessor.md) | provides accessibility to `proxy` | + +## Example + +```cpp +#include +#include +#include + +#include "proxy.h" + +struct Number : pro::facade_builder + ::add_convention, void(int)> + ::add_convention, std::ostream&(std::ostream&) const&> + ::build {}; + +int main() { + pro::proxy p1 = pro::make_proxy(std::numbers::pi); + *p1 *= 3; + std::cout << std::setprecision(10) << *p1 << "\n"; // Prints: 9.424777961 + + pro::proxy p2 = pro::make_proxy(10); + *p2 *= 5; + std::cout << *p2 << "\n"; // Prints: 50 +} +``` + +## See Also + +- [class template `conversion_dispatch`](conversion_dispatch.md) diff --git a/docs/operator_dispatch/accessor.md b/docs/operator_dispatch/accessor.md new file mode 100644 index 0000000..64eda38 --- /dev/null +++ b/docs/operator_dispatch/accessor.md @@ -0,0 +1,111 @@ +# Class template `operator_dispatch::accessor` + +```cpp +// (1) +template +struct accessor { + accessor() = delete; +}; +``` + +`(1)` The default implementation of `accessor` is not constructible. + +For different `Sign` and `Rhs`, `operator_dispatch::accessor` has different specializations. `sop` denotes the sign of operator of each specialization. + +## Left-Hand-Side Operand Specializations + +```cpp +// (2) +template + requires(sizeof...(Os) > 1u && (std::is_trivial_v> && ...)) +struct accessor : accessor... { + using accessor::operator sop...; +}; +``` + +`(2)` When `sizeof...(Os)` is greater than `1`, and `accessor...` are trivial types, inherits all `accessor...` types and `using` their `operator sop`. + +When `Rhs` is `false`, the other specializations are defined as follows, where `sizeof...(Os)` is `1` and the only type `O` qualified with `cv ref noex` (let `SELF` be `std::forward(*this)`): + +### Regular SOPs + +When `Sign` is one of `"+"`, `"-"`, `"*"`, `"/"`, `"%"`, `"++"`, `"--"`, `"=="`, `"!="`, `">"`, `"<"`, `">="`, `"<="`, `"<=>"`, `"&&"`, `"||"`, `"&"`, `"|"`, `"^"`, `"<<"`, `">>"`, `","`, `"->*"`, `"()"`, `"[]"`, + +```cpp +// (3) +template +struct accessor { + R operator sop (Args... args) cv ref noex; +} +``` + +`(3)` Provides an `operator sop(Args...)` with the same *cv ref noex* specifiers as of the overload type. `accessor::operator sop(Args...)` is equivalent to `return proxy_invoke(access_proxy(SELF), std::forward(args)...)`. + +### `!` and `~` + +When `Sign` is either `!` and `~`, + +```cpp +// (4) +template +struct accessor { + R operator sop () cv ref noex; +} +``` + +`(4)` Provides an `operator sop()` with the same *cv ref noex* specifiers as of the overload type. `accessor::operator sop()` is equivalent to `return proxy_invoke(access_proxy(SELF))`. + +### Assignment SOPs + +When `Sign` is one of `"+="`, `"-="`, `"*="`, `"/="`, `"&="`, `"|="`, `"^="`, `"<<="`, `">>="`, + +```cpp +// (5) +template +struct accessor { + /* see below */ operator sop (Arg arg) cv ref noex; +} +``` + +`(4)` Provides an `operator sop(Arg)` with the same *cv ref noex* specifiers as of the overload type. `accessor::operator sop(Arg)` calls `proxy_invoke(access_proxy(SELF), std::forward(arg))` and returns `access_proxy(SELF)` when `C::is_direct` is `true`, or otherwise, returns `*access_proxy(SELF)` when `C::is_direct` is `false`. + +## Right-Hand-Side Operand Specializations + +```cpp +// (6) +template + requires(sizeof...(Os) > 1u && (std::is_trivial_v> && ...)) +struct accessor : accessor... {}; +``` + +`(6)` When `sizeof...(Os)` is greater than `1`, and `accessor...` are trivial types, inherits all `accessor...` types. + +When `Rhs` is `true`, the other specializations are defined as follows, where `sizeof...(Os)` is `1` and the only type `O` qualified with `cv ref noex` (let `SELF` be `std::forward(self)`): + +### Regular SOPs + +When `Sign` is one of `"+"`, `"-"`, `"*"`, `"/"`, `"%"`, `"=="`, `"!="`, `">"`, `"<"`, `">="`, `"<="`, `"<=>"`, `"&&"`, `"||"`, `"&"`, `"|"`, `"^"`, `"<<"`, `">>"`, `","`, `"->*"`, + +```cpp +// (7) +template +struct accessor { + friend R operator sop (Arg arg, accessor cv ref self) noex; +} +``` + +`(7)` Provides a `friend operator sop(Arg arg, accessor cv ref)` with the same *noex* specifiers as of the overload type. `accessor::operator sop(Arg arg, accessor cv ref)` is equivalent to `return proxy_invoke(access_proxy(SELF), std::forward(arg))`. + +### Assignment SOPs + +When `Sign` is one of `"+="`, `"-="`, `"*="`, `"/="`, `"&="`, `"|="`, `"^="`, `"<<="`, `">>="`, + +```cpp +// (8) +template +struct accessor { + friend /* see below */ operator sop (Arg arg, accessor cv ref self) noex; +} +``` + +`(8)` Provides a `friend operator sop(Arg arg, accessor cv ref)` with the same *noex* specifiers as of the overload type. `accessor::operator sop(Arg arg, accessor cv ref)` calls `proxy_invoke(access_proxy(SELF), std::forward(arg))` and returns `access_proxy(SELF)` when `C::is_direct` is `true`, or otherwise, returns `*access_proxy(SELF)` when `C::is_direct` is `false`. diff --git a/docs/operator_dispatch/operator_call.md b/docs/operator_dispatch/operator_call.md new file mode 100644 index 0000000..37e646a --- /dev/null +++ b/docs/operator_dispatch/operator_call.md @@ -0,0 +1,130 @@ +# `operator_dispatch::operator()` + +For different `Sign` and `Rhs`, `operator_dispatch::operator()` are defined differently. `sop` denotes the sign of operator of each specialization. + +## Left-Hand-Side Operand Specializations + +When `Rhs` is `false`, `operator()` of the specializations are defined as follows: + +### Unary & Binary SOPs + +When `Sign` is one of `"+"`, `"-"`, `"*"`, `"&"`, + +```cpp +// (1) +template +/* see below */ operator()(T&& self) + noexcept(noexcept(sop std::forward(self))) + requires(requires { sop std::forward(self); }); + +// (2) +template +/* see below */ operator()(T&& self, Arg&& arg) + noexcept(noexcept(std::forward(self) sop std::forward(arg))) + requires(requires { std::forward(self) sop std::forward(arg); }); +``` + +`(1)` Returns `sop std::forward(self)`. + +`(2)` Returns `std::forward(self) sop std::forward(arg)`. + +### Binary SOPs + +When `Sign` is one of `"/"`, `"%"`, `"=="`, `"!="`, `">"`, `"<"`, `">="`, `"<="`, `"<=>"`, `"&&"`, `"||"`, `"|"`, `"^"`, `"<<"`, `">>"`, `"+="`, `"-="`, `"*="`, `"/="`, `"&="`, `"|="`, `"^="`, `"<<="`, `">>="`, `","`, `"->*"`, + +```cpp +// (3) +template +/* see below */ operator()(T&& self, Arg&& arg) + noexcept(noexcept(std::forward(self) sop std::forward(arg))) + requires(requires { std::forward(self) sop std::forward(arg); }); +``` + +`(3)` Returns `std::forward(self) sop std::forward(arg)`. + +### `"++"` and `"--"` + +When `Sign` is either `"++"`, `"--"`, + +```cpp +// (4) +template +/* see below */ operator()(T&& self) + noexcept(noexcept(sop std::forward(self))) + requires(requires { sop std::forward(self); }); + +// (5) +template +/* see below */ operator()(T&& self, int) + noexcept(noexcept(std::forward(self) sop)) + requires(requires { std::forward(self) sop; }); +``` + +`(4)` Returns `sop std::forward(self)`. + +`(5)` Returns `std::forward(self) sop`. + +### `"!"` and `"~"` + +When `Sign` is either `"!"`, `"~"`, + +```cpp +// (6) +template +/* see below */ operator()(T&& self) + noexcept(noexcept(sop std::forward(self))) + requires(requires { sop std::forward(self); }); +``` + +`(6)` Returns `sop std::forward(self)`. + +### `"()"` + +When `Sign` is `"()"`, + +```cpp +// (7) +template +/* see below */ operator()(T&& self, Args&&... args) + noexcept(noexcept(std::forward(self)(std::forward(args)...))) + requires(requires { std::forward(self)(std::forward(args)...); }); +``` + +`(7)` Returns `std::forward(self)(std::forward(args)...)`. + + +### `"[]"` + +When `Sign` is `"[]"`, + +```cpp +// (8) (until C++23) +template +/* see below */ operator()(T&& self, Args&&... args) + noexcept(noexcept(std::forward(self)[std::forward(args)...])) + requires(requires { std::forward(self)[std::forward(args)...]; }); + +// (9) (since C++23) +template +/* see below */ operator()(T&& self, Arg&& arg) + noexcept(noexcept(std::forward(self)[std::forward(arg)])) + requires(requires { std::forward(self)[std::forward(arg)]; }); +``` + +`(8)` Returns `std::forward(self)[std::forward(args)...]`. Requires the [multidimensional subscript feature](https://en.cppreference.com/w/cpp/language/operators#Array_subscript_operator) in C++23. + +`(9)` Returns `std::forward(self)[std::forward(arg)]`. + +## Right-Hand-Side Operand Specializations + +When `Sign` is one of `"+"`, `"-"`, `"*"`, `"/"`, `"%"`, `"=="`, `"!="`, `">"`, `"<"`, `">="`, `"<="`, `"<=>"`, `"&&"`, `"||"`, `"&"`, `"|"`, `"^"`, `"<<"`, `">>"`, `"+="`, `"-="`, `"*="`, `"/="`, `"&="`, `"|="`, `"^="`, `"<<="`, `">>="`, `","`, `"->*"`, and `Rhs` is `true`, + +```cpp +// (10) +template +/* see below */ operator()(T&& self, Arg&& arg) + noexcept(noexcept(std::forward(arg) sop std::forward(self))) + requires(requires { std::forward(arg) sop std::forward(self); }); +``` + +`(10)` Returns `std::forward(arg) sop std::forward(self)`. diff --git a/docs/proxiable.md b/docs/proxiable.md new file mode 100644 index 0000000..1ec03f2 --- /dev/null +++ b/docs/proxiable.md @@ -0,0 +1,35 @@ +# Concept `proxiable` + +```cpp +template +concept proxiable = /* see-below */; +``` + +The concept `proxiable` specifies that [`proxy`](proxy.md) can potentially contain a value of type `P`. For a type `P`, if `P` is an incomplete type, the behavior of evaluating `proxiable` is undefined. `proxiable` is `true` when `F` meets the [*ProFacade* requirements](ProFacade.md) of `P`; otherwise, it is `false`. + +## Example + +```cpp +#include +#include + +#include "proxy.h" + +PRO_DEF_FREE_DISPATCH(FreeToString, std::to_string, ToString); + +struct Stringable : pro::facade_builder + ::add_convention + ::build {}; + +int main() { + static_assert(pro::proxiable); + static_assert(pro::proxiable, Stringable>); + static_assert(!pro::proxiable*, Stringable>); +} +``` + +## See Also + +- [class template `proxy`](proxy.md) +- [function template `make_proxy`](make_proxy.md) +- [concept `inplace_proxiable_target`](inplace_proxiable_target.md) diff --git a/docs/proxiable_ptr_constraints.md b/docs/proxiable_ptr_constraints.md new file mode 100644 index 0000000..614eef8 --- /dev/null +++ b/docs/proxiable_ptr_constraints.md @@ -0,0 +1,52 @@ +# Class `proxiable_ptr_constraints` + +```cpp +struct proxiable_ptr_constraints; +``` + +## Member objects + +| Name | Type | +| ----------------- | ----------------------------------------- | +| `max_size` | `std::size_t` | +| `max_align` | `std::size_t` | +| `copyability` | [`constraint_level`](constraint_level.md) | +| `relocatability` | [`constraint_level`](constraint_level.md) | +| `destructibility` | [`constraint_level`](constraint_level.md) | + +## Constraints to a Pointer Type + +Given a pointer type `P` and a value `C` of type `proxiable_ptr_constraints`, `P` meets the constraints of `C` if all the following expressions are `true`. + +**Layout constraints**: `sizeof(P) <= C::max_size && alignof(P) <= C::max_align`. + +**Copyability constraints**: + +| Possible value of `C::copyability` | Expressions | +| ---------------------------------- | ------------------------------------------------------------ | +| `constraint_level::none` | `true` | +| `constraint_level::nontrivial` | `std::is_copy_constructible_v

` | +| `constraint_level::nothrow` | `std::is_nothrow_copy_constructible_v

` | +| `constraint_level::trivial` | `std::is_trivially_copy_constructible_v

&& std::is_trivially_destructible_v

` | + +**Relocatability constraints**: + +| Possible value of `C::relocatability` | Expressions | +| ------------------------------------- | ------------------------------------------------------------ | +| `constraint_level::none` | `true` | +| `constraint_level::nontrivial` | `std::is_move_constructible_v

&& std::is_destructible_v

` | +| `constraint_level::nothrow` | `std::is_nothrow_move_constructible_v

&& std::is_nothrow_destructible_v

` | +| `constraint_level::trivial` | `std::is_trivially_move_constructible_v

&& std::is_trivially_destructible_v

` | + +**Destructibility constraints**: + +| Possible value of `C::relocatability` | Expressions | +| ------------------------------------- | ------------------------------------- | +| `constraint_level::none` | `true` | +| `constraint_level::nontrivial` | `std::is_destructible_v

` | +| `constraint_level::nothrow` | `std::is_nothrow_destructible_v

` | +| `constraint_level::trivial` | `std::is_trivially_destructible_v

` | + +## See Also + +- [concept `facade`](facade.md) diff --git a/docs/proxy.md b/docs/proxy.md new file mode 100644 index 0000000..71805ef --- /dev/null +++ b/docs/proxy.md @@ -0,0 +1,78 @@ +# Class template `proxy` + +```cpp +template +class proxy; +``` + +Class template `proxy` is a general-purpose polymorphic wrapper for C++ objects. Unlike other polymorphic wrappers in the C++ standard (e.g., [`std::function`](https://en.cppreference.com/w/cpp/utility/functional/function), [`std::move_only_function`](https://en.cppreference.com/w/cpp/utility/functional/move_only_function), [`std::any`](https://en.cppreference.com/w/cpp/utility/any), etc.), `proxy` is based on pointer semantics. It supports flexible lifetime management without runtime [garbage collection (GC)](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)) at runtime, and offers best-in-class code generation quality, extendibility and accessibility. + +To instantiate `proxy`, `F` shall model [concept `facade`](facade.md). As per `facade`, `typename F::convention_types` shall be a [tuple-like](https://en.cppreference.com/w/cpp/utility/tuple/tuple-like) type containing any number of distinct types `Cs`. For each type `C` in `Cs`, if `C` meets the [*ProAccessible* requirements](ProAccessible.md) of `F`, `typename C::template accessor` is inherited by `proxy` when `C::is_direct` is `true`. Otherwise, it is inherited by the return type of [`operator*`](proxy/indirection.md) when `C::is_direct` is `false`. Implementation of accessors can call [`access_proxy`](access_proxy.md) to access the `proxy` object. It is recommended to use [`facade_builder`](basic_facade_builder.md) to define a facade type. + +Any instance of `proxy` at any given point in time either *contains a value* or *does not contain a value*. If a `proxy` *contains a value*, the type of the value shall be a pointer type `P` where [`proxiable`](proxiable.md) is `true`, and the value is guaranteed to be allocated as part of the `proxy` object footprint, i.e. no dynamic memory allocation occurs. However, `P` may allocate during its construction, depending on its implementation. + +## Member functions + +| Name | Description | +| ---------------------------------------------------------- | -------------------------------------------------- | +| [(constructor)](proxy/constructor.md) | constructs a `proxy` object | +| [(destructor)](proxy/destructor.md) | destroys a `proxy` object | +| [`operator=`](proxy/assignment.md) | assigns a `proxy` object | +| [`operator bool`
`has_value`](proxy/operator_bool.md) | checks if the `proxy` contains a value | +| [`reset`](proxy/reset.md) | destroys any contained value | +| [`swap`](proxy/swap.md) | exchanges the contents | +| [`emplace`](proxy/emplace.md) | constructs the contained value in-place | +| [`operator->`
`operator*`](proxy/indirection.md) | accesses the accessors of the indirect conventions | + +## Non-member functions + +| Name | Description | +| ------------------------------------------------- | ------------------------------------------------------------ | +| [`swap`](proxy/friend_swap.md) | overload the [`std::swap`](https://en.cppreference.com/w/cpp/algorithm/swap) algorithm | +| [`operator==`](proxy/friend_operator_equality.md) | compares a `proxy` with `nullptr` | + +## Comparing with Other Standard Polymorphic Wrappers + +The C++ standard includes several polymorphic wrappers, such as [`std::function`](https://en.cppreference.com/w/cpp/utility/functional/function/function), [`std::packaged_task`](https://en.cppreference.com/w/cpp/thread/packaged_task), [`std::any`](https://en.cppreference.com/w/cpp/utility/any/any), and [`std::move_only_function`](https://en.cppreference.com/w/cpp/utility/functional/move_only_function/move_only_function) (as of C++23). `proxy` offers all their useful features and more, with equal or better code generation compared to various STL implementations. + +A key difference is that `proxy` is based on pointer semantics, allowing flexible lifetime management without runtime GC overhead. In C++11, `std::function` and `std::packaged_task` had constructors that accepted custom allocators for performance tuning, but these were [removed in C++17](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0302r1.html) because "the semantics are unclear, and there are technical issues with storing an allocator in a type-erased context and then recovering that allocator later for any allocations needed during copy assignment". These issues do not apply to `proxy` which fully supports custom allocators via [`allocate_proxy`](allocate_proxy.md). + +Another major difference is that `proxy` is open to abstractions. Unlike `std::function`, `std::packaged_task` and `std::move_only_function`, which only abstracts `operator()`, and `std::any`, which only abstracts casting, `proxy` allows users to define any runtime abstraction requirements via [`facade`](facade.md). It is recommended to use [`facade_builder`](basic_facade_builder.md) to define a custom facade with any conventions, reflections, or constraints. + +## Example + +```cpp +#include +#include +#include +#include +#include + +#include "proxy.h" + +PRO_DEF_MEM_DISPATCH(MemAt, at); + +struct Dictionary : pro::facade_builder + ::add_convention + ::build {}; + +// This is a function, rather than a function template +void PrintDictionary(pro::proxy dictionary) { + std::cout << dictionary->at(1) << "\n"; +} + +int main() { + static std::map container1{{1, "hello"}}; + auto container2 = std::make_shared>(); + container2->push_back("hello"); + container2->push_back("world"); + PrintDictionary(&container1); // Prints: "hello" + PrintDictionary(container2); // Prints: "world" +} +``` + +## See Also + +- [concept `proxiable`](proxiable.md) +- [function template `make_proxy`](make_proxy.md) +- [macro `PRO_DEF_MEM_DISPATCH`](PRO_DEF_MEM_DISPATCH.md) diff --git a/docs/proxy/assignment.md b/docs/proxy/assignment.md new file mode 100644 index 0000000..d095425 --- /dev/null +++ b/docs/proxy/assignment.md @@ -0,0 +1,46 @@ +# `proxy::operator=` + +```cpp +// (1) +proxy& operator=(std::nullptr_t) + noexcept(F::constraints.destructibility >= constraint_level::nothrow) + requires(F::constraints.destructibility >= constraint_level::nontrivial); + +// (2) +proxy& operator=(const proxy&) noexcept requires(F::constraints.copyability == + constraint_level::trivial) = default; +proxy& operator=(const proxy& rhs) + noexcept(F::constraints.copyability >= constraint_level::nothrow && + F::constraints.destructibility >= constraint_level::nothrow) + requires((F::constraints.copyability == constraint_level::nontrivial || + F::constraints.copyability == constraint_level::nothrow) && + F::constraints.destructibility >= constraint_level::nontrivial); + +// (3) +proxy& operator=(proxy&& rhs) + noexcept(F::constraints.relocatability >= constraint_level::nothrow && + F::constraints.destructibility >= constraint_level::nothrow) + requires(F::constraints.relocatability >= constraint_level::nontrivial && + F::constraints.destructibility >= constraint_level::nontrivial && + F::constraints.copyability != constraint_level::trivial); + +// (4) +template +proxy& operator=(P&& ptr) + noexcept(std::is_nothrow_constructible_v, P> && + F::constraints.destructibility >= constraint_level::nothrow) + requires(proxiable, F> && + std::is_constructible_v, P> && + F::constraints.destructibility >= constraint_level::nontrivial); +``` + +Assigns a new value to `proxy` or destroys the contained value. + +- `(1)` Destroys the current contained value if it exists. After the call, `*this` does not contain a value. +- `(2)` Copy assignment operator copies the contained value of `rhs` to `*this`. If `rhs` does not contain a value, it destroys the contained value of `*this` (if any) as if by `auto(rhs).swap(*this)`. The copy assignment is trivial when `F::constraints.copyability == constraint_level::trivial` is `true`. +- `(3)` Move assignment operator moves the contained value of `rhs` to `*this`. If `rhs` does not contain a value, it destroys the contained value of `*this` (if any). If the move construction throws when `F::constraints.relocatability == constraint_level::nontrivial`, `*this` does not contain a value. After move assignment, `rhs` is in a valid state with an unspecified value. The move assignment operator does not participate in overload resolution when `F::constraints.copyability == constraint_level::trivial`, falling back to the trivial copy assignment operator. +- `(4)` Let `VP` be `std::decay_t

`. Sets the contained value to an object of type `VP`, direct-non-list-initialized with `std::forward

(ptr)`. + +## Return Value + +`*this`. diff --git a/docs/proxy/constructor.md b/docs/proxy/constructor.md new file mode 100644 index 0000000..1c33516 --- /dev/null +++ b/docs/proxy/constructor.md @@ -0,0 +1,131 @@ +# `proxy::proxy` + +```cpp +// (1) +proxy() noexcept = default; +proxy(std::nullptr_t) noexcept; + +// (2) +proxy(const proxy&) noexcept requires(F::constraints.copyability == + constraint_level::trivial) = default; +proxy(const proxy& rhs) + noexcept(F::constraints.copyability == constraint_level::nothrow) + requires(F::constraints.copyability == constraint_level::nontrivial || + F::constraints.copyability == constraint_level::nothrow); + +// (3) +proxy(proxy&& rhs) + noexcept(F::constraints.relocatability == constraint_level::nothrow) + requires(F::constraints.relocatability >= constraint_level::nontrivial && + F::constraints.copyability != constraint_level::trivial); + +// (4) +template +proxy(P&& ptr) noexcept(std::is_nothrow_constructible_v, P>) + requires(proxiable, F> && + std::is_constructible_v, P>); + +// (5) +template P, class... Args> +explicit proxy(std::in_place_type_t

, Args&&... args) + noexcept(std::is_nothrow_constructible_v) + requires(std::is_constructible_v); + +// (6) +template P, class U, class... Args> +explicit proxy(std::in_place_type_t

, std::initializer_list il, + Args&&... args) + noexcept(std::is_nothrow_constructible_v< + P, std::initializer_list&, Args...>) + requires(std::is_constructible_v&, Args...>); +``` + +Creates a new `proxy`. + +- `(1)` Default constructor and the constructor taking `nullptr` construct a `proxy` that does not contain a value. +- `(2)` Copy constructor constructs a `proxy` whose contained value is that of `rhs` if `rhs` contains a value, or otherwise, constructs a `proxy` that does not contain a value. As per the `requires` clause, the copy constructor is trivial when `F::constraints.copyability == constraint_level::trivial`. +- `(3)` Move constructor constructs a `proxy` whose contained value is that of `rhs` if `rhs` contains a value, or otherwise, constructs a `proxy` that does not contain a value. `rhs` is in a valid but unspecified state after move construction. As per the `requires` clause, the move constructor does not participate in overload resolution when `F::constraints.copyability == constraint_level::trivial`, so that a move construction falls back to the trivial copy constructor. +- `(4)` Let `VP` be `std::decay_t

`. Constructor taking a value of pointer constructs a `proxy` whose contained value is of type `VP` and direct-non-list-initialized with `std::forward

(ptr)`. +- `(5)` Constructs a `proxy` whose contained value is of type `P` and direct-non-list-initialized with `std::forward(args)...`. +- `(6)` Constructs a `proxy` whose contained value is of type `P` and direct-non-list-initialized with `il, std::forward(args)...`. + +## Comparing with Other Standard Polymorphic Wrappers + +The constructors of `proxy` are similar to but have certain differences from other polymorphic wrappers in the standard, specifically, [`std::any`](https://en.cppreference.com/w/cpp/utility/any/any), and [`std::move_only_function`](https://en.cppreference.com/w/cpp/utility/functional/move_only_function/move_only_function). + +[`std::function`](https://en.cppreference.com/w/cpp/utility/functional/function/function) was introduced in C++11. Comparing its constructors with `proxy`: + +- It forces the target type to be copy constructible, even if its copy constructor is not used in a certain context (which motivated the introduction of `std::move_only_function` in C++23). +- It only supports `Callable` types, while `proxy` supports any pointer type that satisfies [`proxiable`](../proxiable.md). +- It does not have overloads that take `std::in_place_type_t` to construct a value in-place. +- It does not have a conditional default copy constructor, which is efficient for trivial types. +- It used to have several overloads that took an additional allocator, but these were [removed in C++17](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0302r1.html) because "the semantics are unclear, and there are technical issues with storing an allocator in a type-erased context and then recovering that allocator later for any allocations needed during copy assignment". Although the constructors of `proxy` do not explicitly take custom allocator types, we believe this is a useful scenario and provided full support via [`allocate_proxy`](../allocate_proxy.md). +- It forces `noexcept` specifiers, which is mitigated in `std::move_only_function`. + +[`std::any`](https://en.cppreference.com/w/cpp/utility/any/any) was introduced in C++17. Comparing its constructors with `proxy`: + +- Similar with `std::function`, It forces the target type to be copy constructible, even if its copy constructor is not used in a certain context. +- Similar with `std::function`, it does not have a conditional default copy constructor, which is efficient for trivial types. +- It does not support allocators. +- Similar with `std::function`, it forces `noexcept` specifiers. +- In the overloads that take `std::in_place_type_t` to construct a value in-place, the value type is obtained via `std::decay_t` rather than bare `T`, which complicates the semantics and we do not believe is useful. + +[`std::move_only_function`](https://en.cppreference.com/w/cpp/utility/functional/move_only_function/move_only_function) was introduced in C++23. Comparing its constructors with `proxy`: + +- As its name suggests, it is not copyable at all. `proxy` is conditionally copyable depending on the implementation of the given facade type `F`. +- Similar with `std::function`, it only supports `Callable` types, while `proxy` support any pointer type that satisfies [`proxiable`](../proxiable.md). +- Similar with `std::any`, it does not support allocators. +- Similar with `std::any`, in the overloads that take `std::in_place_type_t` to construct a value in-place, the value type is obtained via `std::decay_t` rather than bare `T`, which complicates the semantics and we do not believe is useful. + +## Example + +```cpp +#include +#include +#include + +#include "proxy.h" + +PRO_DEF_MEM_DISPATCH(MemSize, size); +PRO_DEF_MEM_DISPATCH(MemClear, clear); + +struct BasicContainer : pro::facade_builder + ::add_convention + ::add_convention + ::support_copy + ::build {}; + +int main() { + std::vector v{1, 2, 3}; + + pro::proxy p0; + std::cout << std::boolalpha << p0.has_value() << "\n"; // Prints "false" + + // Construct a proxy with a raw pointer + pro::proxy p1 = &v; + std::cout << p1.has_value() << ", " << p1->size() << "\n"; // Prints "true,3" + + // Construct a proxy with a smart pointer + pro::proxy p2 = std::make_shared>(10); + std::cout << p2.has_value() << ", " << p2->size() << "\n"; // Prints "true,10" + + // Copy construction + pro::proxy p3 = p2; + std::cout << p3.has_value() << ", " << p3->size() << "\n"; // Prints "true,10" + + // Move construction + pro::proxy p4 = std::move(p3); + std::cout << p4.has_value() << ", " << p4->size() << "\n"; // Prints "true,10" + + // p3 no longer contains a value + std::cout << p3.has_value() << "\n"; // Prints "false" + + // p2 and p4 shares the same object of std::deque + p2->clear(); + std::cout << p4.has_value() << ", " << p4->size() << "\n"; // Prints "true,0" +} +``` + +## See Also + +- [concept `proxiable`](../proxiable.md) diff --git a/docs/proxy/destructor.md b/docs/proxy/destructor.md new file mode 100644 index 0000000..2e85df2 --- /dev/null +++ b/docs/proxy/destructor.md @@ -0,0 +1,29 @@ +# `proxy::~proxy` + +```cpp +~proxy() requires(F::constraints.destructibility == constraint_level::trivial) + = default; +~proxy() noexcept(F::constraints.destructibility == constraint_level::nothrow) + requires(F::constraints.destructibility == constraint_level::nontrivial || + F::constraints.destructibility == constraint_level::nothrow); +``` + +Destroys the `proxy` object. If the `proxy` contains a value, the contained value is also destroyed. The destructor is trivial when `F::constraints.destructibility` is `constraint_level::trivial`. + +## Example + +```cpp +#include + +#include "proxy.h" + +struct AnyMovable : pro::facade_builder::build {}; + +struct Foo { + ~Foo() { puts("Destroy Foo"); } +}; + +int main() { + pro::proxy p = pro::make_proxy(); +} // The destructor of `Foo` is called when `p` is destroyed +``` diff --git a/docs/proxy/emplace.md b/docs/proxy/emplace.md new file mode 100644 index 0000000..4150b74 --- /dev/null +++ b/docs/proxy/emplace.md @@ -0,0 +1,75 @@ +# `proxy::emplace` + +```cpp +// (1) +template P, class... Args> +P& emplace(Args&&... args) + noexcept(std::is_nothrow_constructible_v && + F::constraints.destructibility >= constraint_level::nothrow) + requires(std::is_constructible_v && + F::constraints.destructibility >= constraint_level::nontrivial); + +// (2) +template P, class U, class... Args> +P& emplace(std::initializer_list il, Args&&... args) + noexcept(std::is_nothrow_constructible_v< + P, std::initializer_list&, Args...> && + F::constraints.destructibility >= constraint_level::nothrow) + requires(std::is_constructible_v&, Args...> && + F::constraints.destructibility >= constraint_level::nontrivial); +``` + +The `emplace` function templates change the contained value to an object of type `P` constructed from the arguments. + +First, the current contained value (if any) is destroyed as if by calling [reset()](reset.md). Then: + +- `(1)` Sets the contained value to an object of type `P` and direct-non-list-initialized with `std::forward(args)...`. +- `(2)` Sets the contained value to an object of type `P` and direct-non-list-initialized with `il, std::forward(args)...`. + +## Return Value + +A reference to the newly created contained value. + +## Exceptions + +Throws any exception thrown by `P`'s constructor. If an exception is thrown, the previously contained value (if any) is destroyed, and `*this` does not contain a value. + +## Comparison with Other Standard Polymorphic Wrappers + +Similar to [`std::any::emplace`](https://en.cppreference.com/w/cpp/utility/any/emplace), `proxy` supports in-place construction for the contained value. However, unlike `std::any`, the overload of `proxy::emplace` that takes a value of type `std::in_place_type_t` creates a value of type `T` rather than `std::decay_t`. This makes the semantics of `proxy::emplace` simpler than those of [`std::any::emplace`](https://en.cppreference.com/w/cpp/utility/any/emplace). + +## Example + +```cpp +#include +#include +#include + +#include "proxy.h" + +struct AnyCopyable : pro::facade_builder + ::support_copy + ::build {}; + +struct Foo { + ~Foo() { puts("Destroy Foo"); } + + int payload[10000]; +}; + +int main() { + static std::pmr::unsynchronized_pool_resource my_memory_pool; + + std::pmr::polymorphic_allocator<> alloc{&my_memory_pool}; + auto deleter = [alloc](auto* ptr) mutable { alloc.delete_object(ptr); }; + + pro::proxy p0; + p0.emplace>(alloc.new_object(), deleter); + pro::proxy p1 = p0; // `Foo` is not copied. Only the reference count is increased. +} // The destructor of `Foo` is called once when both `p0` and `p1` are destroyed +``` + +## See Also + +- [(constructor)](proxy.md) +- [`reset`](reset.md) diff --git a/docs/proxy/friend_operator_equality.md b/docs/proxy/friend_operator_equality.md new file mode 100644 index 0000000..7d23309 --- /dev/null +++ b/docs/proxy/friend_operator_equality.md @@ -0,0 +1,38 @@ +# Function `operator==` (`proxy`) + +```cpp +friend bool operator==(const proxy& lhs, std::nullptr_t) noexcept; +``` + +Checks whether `lhs` contains a value by comparing it with `nullptr`. A `proxy` that does not contain a value compares equal to `nullptr`; otherwise, it compares non-equal. + +This function is not visible to ordinary [unqualified](https://en.cppreference.com/w/cpp/language/unqualified_lookup) or [qualified lookup](https://en.cppreference.com/w/cpp/language/qualified_lookup). It can only be found by [argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl) when `proxy` is an associated class of the arguments. + +The `!=` operator is [synthesized](https://en.cppreference.com/w/cpp/language/default_comparisons) from `operator==`. + +## Return Value + +`!lhs.has_value()`. + +## Example + +```cpp +#include + +#include "proxy.h" + +struct AnyMovable : pro::facade_builder::build {}; + +int main() { + pro::proxy p; + std::cout << std::boolalpha << (p == nullptr) << "\n"; // Prints "true" + std::cout << (p != nullptr) << "\n"; // Prints "false" + p = std::make_unique(123); + std::cout << (p == nullptr) << "\n"; // Prints "false" + std::cout << (p != nullptr) << "\n"; // Prints "true" +} +``` + +## See Also + +- [`has_value`](has_value.md) diff --git a/docs/proxy/friend_swap.md b/docs/proxy/friend_swap.md new file mode 100644 index 0000000..637dbcc --- /dev/null +++ b/docs/proxy/friend_swap.md @@ -0,0 +1,35 @@ +# Function `swap` (`proxy`) + +```cpp +friend void swap(proxy& lhs, proxy& rhs) noexcept(noexcept(lhs.swap(rhs))); +``` + +Overloads the [std::swap](https://en.cppreference.com/w/cpp/algorithm/swap) algorithm for `proxy`. Exchanges the state of `lhs` with that of `rhs`. Effectively calls `lhs.swap(rhs)`. + +This function is not visible to ordinary [unqualified](https://en.cppreference.com/w/cpp/language/unqualified_lookup) or [qualified lookup](https://en.cppreference.com/w/cpp/language/qualified_lookup). It can only be found by [argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl) when `proxy` is an associated class of the arguments. + +## Example + +```cpp +#include +#include +#include + +#include "proxy.h" + +PRO_DEF_FREE_DISPATCH(FreeToString, std::to_string, ToString); + +struct Stringable : pro::facade_builder + ::add_convention + ::build {}; + +int main() { + pro::proxy p0 = pro::make_proxy(123); + pro::proxy p1 = pro::make_proxy(std::numbers::pi); + std::cout << ToString(*p0) << "\n"; // Prints "10" + std::cout << ToString(*p1) << "\n"; // Prints "3.14..." + std::ranges::swap(p0, p1); // finds the hidden friend + std::cout << ToString(*p0) << "\n"; // Prints "3.14..." + std::cout << ToString(*p1) << "\n"; // Prints "10" +} +``` diff --git a/docs/proxy/indirection.md b/docs/proxy/indirection.md new file mode 100644 index 0000000..42ff48b --- /dev/null +++ b/docs/proxy/indirection.md @@ -0,0 +1,70 @@ +# `proxy::operator->`, `proxy::operator*` + +The definitions of `proxy::operator->` and `proxy::operator*` make use of the following exposition-only constant and type alias: + +```cpp +static constexpr bool has-indirection = see below; // exposition only +using indirect-accessor = see below; // exposition only +``` + +As per [`facade`](../facade.md), `typename F::convention_types` shall be a [tuple-like](https://en.cppreference.com/w/cpp/utility/tuple/tuple-like) type containing any number of distinct types `Cs`. Let `Cs2` be the types in `Cs` where each type `C` meets the [*ProAccessible* requirements](../ProAccessible.md) of `F` and `C::is_direct` is `false`. *has-indirection* is `true` if `Cs2` contains at least one type; otherwise, it is `false`. *indirect-accessor* is a non-copyable type that inherits from every type in `Cs2`. + +```cpp +// (1) +indirect-accessor* operator->() noexcept requires(has-indirection); +const indirect-accessor* operator->() const noexcept requires(has-indirection); + +// (2) +indirect-accessor& operator*() & noexcept requires(has-indirection); +const indirect-accessor& operator*() const& noexcept requires(has-indirection); +indirect-accessor&& operator*() && noexcept requires(has-indirection); +const indirect-accessor&& operator*() const&& noexcept requires(has-indirection); +``` + +These operators access the accessors of the indirect conventions, as if dereferencing the contained value. + +- `(1)` Returns a pointer to the *indirect-accessor*. +- `(2)` Returns a reference to the *indirect-accessor*. + +The behavior is undefined if `*this` does not contain a value. + +## Notes + +These operators do not check whether the `proxy` contains a value. To check whether the `proxy` contains a value, call [`has_value()`](has_value.md) or use [operator ==](friend_operator_equality.md). + +## Example + +```cpp +#include +#include +#include + +#include "proxy.h" + +PRO_DEF_MEM_DISPATCH(MemSize, size); + +struct BasicContainer : pro::facade_builder + ::add_convention + ::build {}; + +PRO_DEF_FREE_DISPATCH(FreeToString, std::to_string, ToString); + +struct Stringable : pro::facade_builder + ::add_convention + ::build {}; + +int main() { + std::vector v(10); + pro::proxy p0 = &v; + std::cout << p0->size() << "\n"; // Prints "10" + std::cout << (*p0).size() << "\n"; // Prints "10" + + pro::proxy p1 = pro::make_proxy(123); + std::cout << ToString(*p1) << "\n"; // Prints "123" +} +``` + +## See Also + +- [function template `access_proxy`](../access_proxy.md) +- [function template `proxy_invoke`](../proxy_invoke.md) diff --git a/docs/proxy/operator_bool.md b/docs/proxy/operator_bool.md new file mode 100644 index 0000000..672c8b4 --- /dev/null +++ b/docs/proxy/operator_bool.md @@ -0,0 +1,35 @@ +# `proxy::operator bool`, `proxy::has_value` + +```cpp +explicit operator bool() const noexcept; +bool has_value() const noexcept; +``` + +Checks whether `*this` contains a value. + +## Return Value + +`true` if `*this` contains a value, or `false` otherwise. + +## Example + +```cpp +#include + +#include "proxy.h" + +struct AnyMovable : pro::facade_builder::build {}; + +int main() { + pro::proxy p; + std::cout << std::boolalpha << p.has_value() << "\n"; // Prints "false" + p = pro::make_proxy(123); + std::cout << p.has_value() << "\n"; // Prints "true" + p = nullptr; + std::cout << static_cast(p) << "\n"; // Prints "false" +} +``` + +## See Also + +- [`operator==`](friend_operator_equality.md) diff --git a/docs/proxy/reset.md b/docs/proxy/reset.md new file mode 100644 index 0000000..fedd8af --- /dev/null +++ b/docs/proxy/reset.md @@ -0,0 +1,30 @@ +# `proxy::reset` + +```cpp +void reset() + noexcept(F::constraints.destructibility >= constraint_level::nothrow) + requires(F::constraints.destructibility >= constraint_level::nontrivial); +``` + +Destroys the contained value if it exists. After the call, `*this` does not contain a value. + +## Example + +```cpp +#include + +#include "proxy.h" + +struct AnyMovable : pro::facade_builder::build {}; + +int main() { + pro::proxy p = pro::make_proxy(123); + std::cout << std::boolalpha << p.has_value() << "\n"; // Prints "true" + p.reset(); + std::cout << p.has_value() << "\n"; // Prints "false" +} +``` + +## See Also + +- [`operator=`](assignment.md) diff --git a/docs/proxy/swap.md b/docs/proxy/swap.md new file mode 100644 index 0000000..ad8a38c --- /dev/null +++ b/docs/proxy/swap.md @@ -0,0 +1,11 @@ +# `proxy::swap` + +```cpp +void swap(proxy& rhs) + noexcept(F::constraints.relocatability >= constraint_level::nothrow || + F::constraints.copyability == constraint_level::trivial) + requires(F::constraints.relocatability >= constraint_level::nontrivial || + F::constraints.copyability == constraint_level::trivial); +``` + +Exchanges the contained values of `*this` and `rhs`. diff --git a/docs/proxy_invoke.md b/docs/proxy_invoke.md new file mode 100644 index 0000000..378a8c9 --- /dev/null +++ b/docs/proxy_invoke.md @@ -0,0 +1,65 @@ +# Function template `proxy_invoke` + +```cpp +template +/* see below */ proxy_invoke(proxy& p, Args&&... args); + +template +/* see below */ proxy_invoke(const proxy& p, Args&&... args); + +template +/* see below */ proxy_invoke(proxy&& p, Args&&... args); + +template +/* see below */ proxy_invoke(const proxy&& p, Args&&... args); +``` + +Invokes a `proxy` with a specified convention type and arguments. `C` is required to be defined in `typename F::convention_types`. Overload resolution is performed among the overload types defined in `typename C::overload_types`. + +Let `ptr` be the contained value of `p` with the same cv ref-qualifiers, `O` be the matched overload type among the overload types defined in `typename C::overload_types`, `Args2...` be the argument types of `O`, `R` be the return type of `O`, + +- if `C::is_direct` is `true`, let `v` be `std::forward(ptr)`, or otherwise, +- if `C::is_direct` is `false`, let `v` be `*std::forward(ptr)`, + +equivalent to: + +- [`INVOKE`](https://en.cppreference.com/w/cpp/utility/functional)`(typename C::dispatch_type{}, std::forward(v), static_cast(args)...)` if the expression is well-formed, or otherwise, +- [`INVOKE`](https://en.cppreference.com/w/cpp/utility/functional)`(typename C::dispatch_type{}, nullptr, static_cast(args)...)`. + +The behavior is undefined if `p` does not contain a value. + +## Notes + +It is generally not recommended to call `proxy_invoke` directly. Using an [`accessor`](ProAccessible.md) is usually a better option with easier and more descriptive syntax. If the facade type `F` is defined with the recommended facilities, it has full accessibility support. Specifically, when + +- the underlying dispatch type `typename C::dispatch_type` is defined via [macro `PRO_DEF_MEM_DISPATCH`](PRO_DEF_MEM_DISPATCH.md), [macro `PRO_DEF_FREE_DISPATCH`](PRO_DEF_FREE_DISPATCH.md), or is a specialization of either [`operator_dispatch`](operator_dispatch.md) or [`conversion_dispatch`](conversion_dispatch.md), and +- the convention is defined via [`facade_builder`](basic_facade_builder.md). + +## Example + +```cpp +#include +#include + +#include "proxy.h" + +PRO_DEF_FREE_DISPATCH(FreeToString, std::to_string, ToString); + +struct Stringable : pro::facade_builder + ::add_convention + ::build {}; + +int main() { + int a = 123; + pro::proxy p = &a; + std::cout << ToString(*p) << "\n"; // Invokes with accessor, prints: "123" + + using C = std::tuple_element_t<0u, Stringable::convention_types>; + std::cout << pro::proxy_invoke(p) << "\n"; // Invokes with proxy_invoke, also prints: "123" +} +``` + +## See Also + +- [function template `access_proxy`](access_proxy.md) +- [function template `proxy_reflect`](proxy_reflect.md) diff --git a/docs/proxy_reflect.md b/docs/proxy_reflect.md new file mode 100644 index 0000000..b94a6f5 --- /dev/null +++ b/docs/proxy_reflect.md @@ -0,0 +1,49 @@ +# Function template `proxy_reflect` + +```cpp +template +const R& proxy_reflect(const proxy& p) noexcept; +``` + +Retrieves a value of type `R` constructed from [`std::in_place_type

`](https://en.cppreference.com/w/cpp/utility/in_place), where `P` is the type of the contained value of `p`. `R` is required to be defined in `typename F::reflection_types`. The behavior is undefined if `p` does not contain a value. + +The reference obtained from `proxy_reflect()` may be invalidated if `p` is subsequently modified. + +## Notes + +This function is useful when only metadata deduced from a type is needed. While [`proxy_invoke`](proxy_invoke.md) can also retrieve type metadata, `proxy_reflect` can generate more efficient code in this context. + +## Example + +```cpp +#include +#include +#include + +#include "proxy.h" + +struct TraitsRefl { + template + constexpr explicit TraitsRefl(std::in_place_type_t

) + : Copyable(std::is_copy_constructible_v

) {} + + const bool Copyable; +}; + +struct TestFacade : pro::facade_builder + ::add_reflection + ::build {}; + +int main() { + pro::proxy p1 = std::make_unique(); + std::cout << std::boolalpha << pro::proxy_reflect(p1).Copyable << "\n"; // Prints: "false" + + pro::proxy p2 = std::make_shared(); + std::cout << pro::proxy_reflect(p2).Copyable << "\n"; // Prints: "true" +} +``` + +## See Also + +- [alias template `basic_facade_builder::add_reflection`](basic_facade_builder/add_reflection.md) +- [named requirements *ProReflection*](ProReflection.md) diff --git a/docs/specifications.md b/docs/specifications.md new file mode 100644 index 0000000..d6a9a20 --- /dev/null +++ b/docs/specifications.md @@ -0,0 +1,55 @@ +# Proxy 3 Specifications + +This document provides the API specifications for the C++ library Proxy (version 3). All the documented concepts, classes, and functions are defined in the namespace `pro`. Unless otherwise specified, all facilities are [freestanding](https://en.cppreference.com/w/cpp/freestanding) by default. + +## Concepts + +| Name | Description | +| --------------------------------------------------------- | ------------------------------------------------------------ | +| [`facade`](facade.md) | Specifies that a type models a "facade" | +| [`proxiable`](proxiable.md) | Specifies that a pointer type can instantiate a `proxy` | +| [`inplace_proxiable_target`](inplace_proxiable_target.md) | Specifies that a value type can instantiate a `proxy` without allocation | + +## Classes + +| Name | Description | +| ------------------------------------------------------------ | ------------------------------------------------------------ | +| [`proxy`](proxy.md) | Wraps a pointer object matching specified facade | +| [`basic_facade_builder`
`facade_builder`](basic_facade_builder.md) | Provides capability to build a facade type at compile-time | +| [`constraint_level`](constraint_level.md) | Defines the 4 constraint levels of a special member function | +| [`proxiable_ptr_constraints`](proxiable_ptr_constraints.md) | Defines the constraints of a pointer type to instantiate a `proxy` | +| [`operator_dispatch`](operator_dispatch.md) | Dispatch type for operator expressions with accessibility | +| [`conversion_dispatch`](conversion_dispatch.md) | Dispatch type for conversion expressions with accessibility | + +## Functions + +| Name | Description | +| --------------------------------------------- | ------------------------------------------------------------ | +| [`make_proxy`](make_proxy.md) | Creates a `proxy` object potentially with heap allocation | +| [`make_proxy_inplace`](make_proxy_inplace.md) | Creates a `proxy` object with strong no-allocation guarantee | +| [`allocate_proxy`](allocate_proxy.md) | Creates a `proxy` object with an allocator | +| [`proxy_invoke`](proxy_invoke.md) | Invokes a `proxy` with a specified convention | +| [`proxy_reflect`](proxy_reflect.md) | Acquires reflection information of the underlying pointer type | +| [`access_proxy`](access_proxy.md) | Accesses a `proxy` object via an accessor | + +## Macros + +| Name | Description | +| --------------------------------------------------- | ------------------------------------------------------------ | +| [`PRO_DEF_MEM_DISPATCH`](PRO_DEF_MEM_DISPATCH.md) | Defines a dispatch type for member function call expressions with accessibility | +| [`PRO_DEF_FREE_DISPATCH`](PRO_DEF_FREE_DISPATCH.md) | Defines a dispatch type for free function call expressions with accessibility | +| [`PRO_DEF_WEAK_DISPATCH`](PRO_DEF_WEAK_DISPATCH.md) | Defines a weak dispatch type with a default implementation | +| [`__msft_lib_proxy`](__msft_lib_proxy.md) | Feature test macro | + +## Named Requirements + +| Name | Description | +| --------------------------------------------- | ------------------------------------------------------------ | +| [*ProBasicFacade*](ProBasicFacade.md) | Specifies that a type potentially models a "facade" of `proxy` | +| [*ProBasicConvention*](ProBasicConvention.md) | Specifies that a type potentially models a "convention" | +| [*ProFacade*](ProFacade.md) | Specifies that a type models a "facade" of `proxy` | +| [*ProConvention*](ProConvention.md) | Specifies that a type models a "convention" | +| [*ProReflection*](ProReflection.md) | Specifies that a type models a "reflection" | +| [*ProDispatch*](ProDispatch.md) | Specifies that a type models a "dispatch" | +| [*ProOverload*](ProOverload.md) | Specifies that a type models an "overload" | +| [*ProAccessible*](ProAccessible.md) | Specifies that a type provides accessibility to `proxy` |