Proxy: Next Generation Polymorphism in C++
Are you looking to simplify the lifetime management and maintenance of polymorphic objects in C++?
Do you want to write polymorphic code in C++ as easily as in GC languages 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.
"Proxy" is a modern C++ library that helps you use polymorphism (a way to use different types of objects interchangeably) without needing inheritance.
"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 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:
Please refer to the Proxy's Frequently Asked Questions for more background, and refer to the specifications for more technical details.
"Proxy" is a header-only C++20 library. To use the library, make sure your compiler meets the minimum requirements and just put the proxy directory in your project's include directory. Alternatively, you can install the library via:
CPM / CMake FetchContent_Declare:
CPMAddPackage(
NAME msft_proxy4
GIT_TAG 4.0.0 # or above
GIT_REPOSITORY https://github.com/microsoft/proxy.git
)
target_link_libraries(main PRIVATE msft_proxy4::proxy)
Let's get started with the following "Hello World" example (run):
…
Here is a step-by-step explanation:
#include <format>: For std::format.#include <iostream>: For std::cout.#include <string>: For std::string.#include <proxy/proxy.h>: For the "Proxy" library. Most of the facilities of the library are defined in namespace pro.struct Formattable : pro::facade_builder ... ::build {}: Defines a facade type Formattable. The term "facade", formally defined as the ProFacade requirements, is how the "Proxy" library models runtime abstraction. Specifically,pro::facade_builder: Provides capability to build a facade type at compile-time.add_skill<pro::skills::format>: Specifies the capability of formatting (via standard formatting functions).build: Builds the context into a facade type.pro::proxy<Formattable> 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::format("*p1 = {}\n", *p1): This is how it works. *p1 is formatted as "Hello World" because the capability was defined in the facade Formattable, so it works as if by calling std::format("*p1 = {}\n", str).pro::proxy<Formattable> p2 = std::make_unique<int>(123): Creates a std::unique_ptr<int> 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<Formattable>, which means you can have a function that returns pro::proxy<Formattable> without exposing any information about the implementation details to its caller.std::format("*p2 = {}\n", *p2): Formats *p2 as "123" with no surprises.pro::proxy<Formattable> p3 = pro::make_proxy<Formattable>(3.14159): Creates a proxy from a double without specifying the underlying pointer type. Specifically,p2, p3 also has ownership of the underlying double value, but can effectively avoid heap allocation.double) is known to be small (on major 32- or 64-bit platforms), pro::make_proxy realizes the fact at compile-time, and falls back to pro::make_proxy_inplace, which guarantees no heap allocation.std::function and other existing polymorphic wrappers in the standard.std::format("*p3 = {:.2f}\n", *p3): Formats *p3 as "3.14" as per the standard format specification with no surprises.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.Note: If you prefer the library to be consumed as a (C++20) module, refer to C++20 Modules support.
In the previous "Hello World" example, we demonstrated how proxy could manage different types of objects and be formatted with std::format. While std::format is not the only option to print objects in C++, can we simply make proxy work with std::cout? The answer is "yes". The previous example is equivalent to the following implementation (run):
…
Here is a step-by-step explanation:
#include <iomanip>: For std::setprecision.
#include <iostream>: For std::cout.
#include <string>: For std::string.
#include <proxy/proxy.h>: For the "Proxy" library.
struct Streamable : pro::facade_builder ... ::build {}: Defines a facade type Streamable. Specifically,
pro::facade_builder: Gets prepared to build another facade.add_convention: Adds a generalized "calling convention", defined by a "dispatch" and several "overloads", to the build context.pro::operator_dispatch<"<<", 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. const specifies that the primary operand is const.build: Builds the context into a facade type.pro::proxy<Streamable> p1 = &str: Creates a proxy object from a raw pointer of std::string.
std::cout << *p1: 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<Streamable> p2 = std::make_unique<int>(123): Creates a std::unique_ptr<int> and converts to a proxy.
std::cout << *p2: Prints "123" with no surprises.
pro::proxy<Streamable> p3 = pro::make_proxy<Streamable>(3.14): Creates a proxy from a double.
std::cout << std::fixed << std::setprecision(2) << *p3;: Prints "3.14" with no surprises.
In addition to the operator expressions demonstrated in the previous examples, the library supports almost all forms of expressions in C++ and can make them polymorphic. Specifically,
PRO_DEF_MEM_DISPATCH: Defines a dispatch type for member function call expressions, providing accessibility as member functions.PRO_DEF_FREE_DISPATCH: Defines a dispatch type for free function call expressions, providing accessibility as free functions.PRO_DEF_FREE_AS_MEM_DISPATCH: Defines a dispatch type for free function call expressions, providing accessibility as member functions.No open issues yet, or sync has not completed.