We are thrilled to announce that Proxy 3, our latest and greatest solution for polymorphism in C++, is now feature complete! Since the library was initially open-sourced, we have heard much positive feedback and received many brilliant feature requests. Big thanks to all of you who have contributed any code or idea that made the library better!
Our Mission
“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:
- 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, 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 New Look
Comparing to our previous releases, there are some major improvements in the syntax and utilities. Let’s get started with some examples!
Hello World
#include <iostream>
#include <string>
#include "proxy.h"
struct Streamable : pro::facade_builder
::add_convention<pro::operator_dispatch<"<<", true>, std::ostream&(std::ostream& out) const>
::build {};
int main() {
std::string str = "Hello World";
pro::proxy<Streamable> p1 = &str;
std::cout << "p1 = " << *p1 << "\n"; // Prints: "p1 = Hello World"
pro::proxy<Streamable> p2 = std::make_unique<int>(123);
std::cout << "p2 = " << *p2 << "\n"; // Prints: "p2 = 123"
pro::proxy<Streamable> p3 = pro::make_proxy<Streamable>(3.14);
std::cout << "p3 = " << *p3 << "\n"; // Prints: "p3 = 3.14"
}
Here is a step-by-step explanation:
#include <iostream>
: Forstd::cout
.#include <string>
: Forstd::string
.#include "proxy.h"
: For the “Proxy” library. Most of the facilities of the library are defined in namespacepro
. If the library is consumed via vcpkg or conan, this line should be changed into#include <proxy/proxy.h>
.struct Streamable : pro::facade_builder ... ::build {}
: Defines a facade typeStreamable
. The term “facade” is how the “Proxy” library models runtime abstraction. Specifically,pro::facade_builder
: Provides capability to build a facade type at compile-time.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 parametertrue
). 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 withstd::move_only_function
.const
specifies that the primary operand isconst
.build
: Builds the context into a facade type.
pro::proxy<Streamable> p1 = &str
: Creates aproxy
object from a raw pointer ofstd::string
.p1
behaves like a raw pointer, and does not have ownership of the underlyingstd::string
. If the lifetime ofstr
ends beforep1
,p1
becomes dangling.std::cout << *p1
: This is how it works. It prints “Hello World” because the calling convention is defined in the facadeStreamable
, so it works as if by callingstd::cout << str
.pro::proxy<Streamable> p2 =
std::make_unique
<int>(123)
: Creates astd::unique_ptr
<int>
and converts to aproxy
. Different fromp1
,p2
has ownership of the underlyingint
because it is instantiated from a value ofstd::unique_ptr
, and will call the destructor ofstd::unique_ptr
whenp2
is destroyed, whilep1
does not have ownership of the underlyingint
because it is instantiated from a raw pointer.p1
andp2
are of the same typepro::proxy<Streamable>
, which means you can have a function that returnspro::proxy<Streamable>
without exposing any information about the implementation details to its caller.std::cout << *p2
: Prints “123” with no surprise.pro::proxy<Streamable> p3 = pro::make_proxy<Streamable>(3.14)
: Creates aproxy
from adouble
without specifying the underlying pointer type. Specifically,- Similar with
p2
,p3
also has ownership of the underlyingdouble
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
realizes the fact at compile-time and guarantees no heap allocation. - Library “Proxy” explicitly defines when heap allocation occurs or not to avoid users falling into performance hell, which is different from
std::function
and other existing polymorphic wrappers in the standard.
- Similar with
std::cout << *p3
: Prints “3.14” with no surprise.- When
main
returns,p2
andp3
will destroy the underlying objects, whilep1
does nothing because it holds a raw pointer that does not have ownership of the underlyingstd::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,
- The
PRO_DEF_MEM_DISPATCH
macro: Defines a dispatch type for member function call expressions. - The
PRO_DEF_FREE_DISPATCH
macro: Defines a dispatch type for free function call expressions. - The
pro::operator_dispatch
class template: Dispatch type for operator expressions. - The
pro::conversion_dispatch
class template: Dispatch type for conversion expressions.
Note that some facilities are provided as macros, 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:
#include <iostream>
#include <sstream>
#include "proxy.h"
PRO_DEF_MEM_DISPATCH(MemDraw, Draw);
PRO_DEF_MEM_DISPATCH(MemArea, Area);
struct Drawable : pro::facade_builder
::add_convention<MemDraw, void(std::ostream& output)>
::add_convention<MemArea, double() noexcept>
::support_copy<pro::constraint_level::nontrivial>
::build {};
class Rectangle {
public:
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:
double width_;
double height_;
};
std::string PrintDrawableToString(pro::proxy<Drawable> p) {
std::stringstream result;
result << "entity = ";
p->Draw(result);
result << ", area = " << p->Area();
return std::move(result).str();
}
int main() {
pro::proxy<Drawable> p = pro::make_proxy<Drawable, Rectangle>(3, 5);
std::string str = PrintDrawableToString(p);
std::cout << str << "\n"; // Prints: "entity = {Rectangle: width = 3, height = 5}, area = 15"
}
Here is a step-by-step explanation:
#include <iostream>
: Forstd::cout
.#include <sstream>
: Forstd::stringstream
.#include "proxy.h"
: For the “Proxy” library.PRO_DEF_MEM_DISPATCH(MemDraw, Draw)
: Defines a dispatch typeMemDraw
for expressions of calling member functionDraw
.PRO_DEF_MEM_DISPATCH(MemArea, Area)
: Defines a dispatch typeMemArea
for expressions of calling member functionArea
.struct Drawable : pro::facade_builder ... ::build {}
: Defines a facade typeDrawable
. Specifically,add_convention
: Adds calling conventions to the build context.support_copy<pro::constraint_level::nontrivial>
: Specifies the underlying pointer type shall be copyable, which also makes the resultingproxy
type copyable.
class Rectangle
: An implementation ofDrawable
.- Function
PrintDrawableToString
: Converts aDrawable
into astd::string
. Note that this is a function rather than a function template, which means it can generate ABI in a larger build system. pro::proxy<Drawable> p = pro::make_proxy<Drawable, Rectangle>(3, 5)
: Creates aproxy<Drawable>
object containing aRectangle
.std::string str = PrintDrawableToString(p)
: Convertsp
into astd::string
, implicitly creates a copy ofp
.std::cout << str
: Prints the string.
Other Useful Features
In addition to the features mentioned above, here is a curated list of the most popular features based on user feedback:
- Overloading:
facade_builder::add_convention
is more powerful than demonstrated above. It can take any number of overload types and perform standard overload resolution when invoking aproxy
. - Facade composition:
facade_builder::add_facade
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
from an existing dispatch type and a default implementation. - Allocator awareness: Function template
allocate_proxy
is able to create aproxy
from a value with any custom allocator. In C++11,std::function
andstd::packaged_task
had constructors that accepted custom allocators for performance tuning, but these were removed in C++17 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 toallocate_proxy
. - Configurable constraints:
facade_builder
provides full support for constraints configuration, including memory layout (byrestrict_layout
), copyability (bysupport_copy
), relocatability (bysupport_relocation
), and destructibility (bysupport_destruction
). - Reflection:
proxy
supports type-based compile-time reflection for runtime queries, specifically withfacade_builder::add_reflection
and function templateproxy_reflect
.
Summary
We hope this library could empower more C++ users outside Microsoft to write polymorphic code easier. The full documentation of Proxy 3 will be published soon. Please stay tuned for more technical details. We are also actively working on several ISO C++ proposals for further standardization.
Great job! I’ve been exploring how to ramp up the efficiency of backend processing for my web scraping projects, especially those that deal with substantial data loads. I’m particularly interested in the potential application of advanced C++ features. Could Microsoft’s Proxy 3 library, with its modern approach to polymorphism without inheritance, be a game-changer in optimizing high-volume web scraping tasks?
The Proxy 3 library from Microsoft enhances backend processing significantly, and can be useful in web scraping and proxy utilizing systems that handle high data volumes. By leveraging modern C++ features to enable polymorphism without traditional inheritance, it streamlines data management and accelerates processing speeds. These capabilities are crucial for efficiently managing large-scale data extraction and transformation tasks.
Related Resources:
API Reference: https://microsoft.github.io/proxy/docs/specifications.html
Frequently Asked Questions: https://microsoft.github.io/proxy/docs/faq.html
wow! that is awesome…
after the first little games, i stumbled over a propblem:
if only const access needed, how to specify it to a proxy parameter?
maybe there is a simple solution?
sample is here!
If I understand correctly from your code, you are trying to instantiate a `proxy` with a `const` pointer, while the facade has non-const conventions.
<code>
There are two functions in your code, `ModifyDictionary` modifies the value, and `PrintDictionary` does not modify the value.
<code>
In this case, the two functions rely on two different abstraction model. You can define two facades for each of them, e.g.,
<code>
Then, you can refactor the two functions as:
<code>
There is a very similar example in...