Document #: | P3086R5 [Latest] [Status] |
Date: | 2025-10-06 |
Project: | Programming Language C++ |
Audience: |
LEWGI LEWG |
Reply-to: |
Mingxin Wang <mingxwa@outlook.com> |
constraint_level
facade
proxiable
proxy_indirect_accessor
proxy
proxy_invoke
proxy_reflect
substitution_dispatch
observer_facade
and alias template
proxy_view
weak_facade
and alias template
weak_proxy
Abstract
proxy
is a proposed <memory>
facility providing pointer-semantics-based runtime polymorphism. A
facade is a compile-time description of operations (conventions),
reflection hooks, and storage / lifetime constraints. A proxy<F>
holds a pointer-like witness plus facade metadata; concrete types remain
layout- and ABI‑stable and need not participate in an inheritance
hierarchy. The design yields: (1) non-intrusive post‑hoc interface
composition, (2) an owning / observing / weak handle spectrum sharing
one callable surface, (3) explicit capability downgrades via
substitution without reconstruction or allocation, (4) predictable
high‑performance dispatch paths (0–1 dependent loads before the indirect
branch, optional metadata embedding, trivial relocation opportunities),
and (5) precise concept‑based diagnostics.
substitution_dispatch
,
proxy_view
together with
observer_facade
, and
weak_proxy
together with
weak_facade
into the wording and
rationale informed by implementation practice.access_proxy
.facade_aware_overload_t
.proxy
. function templates
access_proxy
,
proxy_invoke
,
proxy_reflect
.proxy_indirect_accessor
.proxy::invoke()
and proxy::reflect()
into free functions proxy_invoke()
and
proxy_reflect()
.proxy
(the ProAccessible
requirements and function template access_proxy()
).proxy::operator bool()
,
proxy::operator->()
and proxy::operator*()
.std::swap(proxy, proxy)
into a friend function.As per review comments from LEWGI in Tokyo,
make_proxy
from the proposed
wording.concept facade
,
allowing tuple-like types in the definition of a facade or
dispatch.concept facade
to
allow fallbacks in the invocation of a dispatch.<memory>
.PRO_DEF_MEMBER_DISPATCH_WITH_DEFAULT
and
PRO_DEF_FREE_DISPATCH_WITH_DEFAULT
.noexcept
in
the abstraction model and updated the
noexcept
clause of proxy::invoke
and
proxy::operator()
.concept basic_facade
and the constraints on the class template
proxy
to allow more potential
optimizations in code generation.This paper proposes standard library support for pointer-semantics-based runtime polymorphism. The facility consists of the following standardization scope components:
proxy
– the
pointer-semantics handle mediating polymorphic callsconstraint_level
facade
and
proxiable
facade_aware_overload_t
proxy_invoke
and proxy_reflect
substitution_dispatch
observer_facade
/
proxy_view
and
weak_facade
/
weak_proxy
Together these describe a minimal substrate for expression-oriented runtime polymorphism executed strictly through ordinary pointer semantics. No inheritance or mandatory dynamic allocation is required. The abstraction has shipped in production (including inside Microsoft Windows) since 2022.
Out of scope for this initial paper (but shipped in existing implementations) are higher level facade construction helpers, additional skills (formatting, RTTI extensions, slim layout presets, etc.), and convenience factory functions beyond those required to motivate the core. Excluding them keeps the initial wording minimal and focused on the foundational model; they can be proposed incrementally once consensus on the substrate is reached.
Existing options for runtime polymorphism each impose a different coupling axis:
Existing approach
|
Primary coupling
|
Typical costs
|
Gaps for modern use
|
---|---|---|---|
Virtual functions | Interface <-> object layout & ABI (vptr), inheritance graph | Intrusive base classes, fixed surface, per-object vptr storage | Hard to refactor; ownership orthogonal; extending capability requires hierarchy surgery |
Ad-hoc vtable wrappers | Wrapper <-> bespoke table layout | Repeated machinery, uneven capability/reflection | Ecosystem fragmentation; inconsistent substitution semantics |
Single-purpose type erasure (std::function ,
std::any ) |
Wrapper <-> specific call pattern or value semantics | Often heap allocation; narrow operation set | Cannot compose heterogeneous operation families or ownership modes |
Users still need a facility that simultaneously provides:
A facade is a compile-time description of admissible
operations (“conventions”), associated reflection hooks, and storage /
lifetime constraint parameters (max size, alignment, copy / relocate /
destroy levels). A proxy<F>
pairs a pointer-like witness with facade metadata. Adaptors
(observer_facade
,
weak_facade
) systematically rewrite
an existing facade to strip owning semantics or introduce weak locking
while preserving the call surface where valid.
substitution_dispatch
provides
explicit conversions (including capability downgrades) between proxies
defined by a facade; it is not restricted to structural subset relations
of convention sets. It guarantees null propagation and may exploit
trivial relocatability to avoid invoking user move constructors.
Concepts and named requirements precisely specify when a type can
participate. This aligns with a well‑understood existential pattern:
informally, proxy<F>
is an existential wrapper over the family of types modeling
ProFacade relative to
F
.
The open‑source implementation (https://github.com/microsoft/proxy) targets C++20 and later and is continuously tested on GCC, Clang, MSVC, NVIDIA HPC, and Intel oneAPI across Windows, Linux, and macOS. The wording tracks version 4 of the shipping library.
Runtime polymorphism is modeled as morphisms between pointer
witnesses. A facade enumerates morphism families (dispatch conventions)
plus constraints ensuring the witness’s storage satisfies the required
lifetime and layout properties. The named requirements
(ProOverload, ProDispatch, etc.) guarantee consistent
metadata formation;
facade_aware_overload_t
captures
recursively facade-aware overload sets. Because witnesses are ordinary
pointers (or pointer-sized handles), the existing object model and
aliasing rules apply unchanged. Compared to the fixed product of
function pointers embedded per object in traditional virtual tables, a
facade is a post-hoc composition boundary: capability
downgrades and adaptations are expressed explicitly via
substitution_dispatch
rather than
implicit hierarchy relationships.
Memory layout and indirection:
proxy<F>
keeps the implementation object’s layout unchanged. The
proxy
stores a pointer-like witness
plus facade metadata (often a pointer to a dispatch table or inlined
table if small). Adding or composing conventions does not require
touching the implementation type; different client code can materialize
different facades over the same underlying type.The following diagrams contrast a typical single-inheritance virtual layout with a proxy-based arrangement wrapping the same implementation object. The implementation type (“Impl”) is identical in both cases; only the surrounding indirection differs.
Object with virtual functions:
flowchart LR subgraph VirtualObject["Object"] VPtr[(vptr)] OImpl[Impl data] end VPtr --> VTable["(vtable)"] VTable --> F1["f()"] VTable --> F2["g()"] VTable --> F3["h()"]
proxy
with indirect storage and
large metadata:
flowchart LR subgraph Proxy[proxy] WP["(witness ptr)"] MD["(metadata)"] end WP --> OImpl[Impl data] MD --> D1["f()"] MD --> D2["g()"] MD --> D3["h()"]
proxy
with direct storage and
large metadata:
flowchart LR subgraph Proxy[proxy] subgraph WP["witness ptr"] OImpl[Impl data] end MD["(metadata)"] end MD --> D1["f()"] MD --> D2["g()"] MD --> D3["h()"]
proxy
with direct storage and
small metadata:
flowchart LR subgraph Proxy["proxy"] subgraph WP["witness ptr"] OImpl[Impl data] end subgraph MD["(metadata)"] D["f()"] end end
Engineering / evolution:
Memory management and heterogeneity:
unique_ptr
,
shared_ptr
), each layered
externally; in-place alternatives require separate wrappers.proxy
can hold any pointer-like
witness object meeting size/alignment/constraint requirements, including
custom allocator-aware handles, offset/arena pointers, or small in-place
storage (via facilities that are part of the deployed library but out of
scope for standardization here). Borrowing
(proxy_view
) and weak
(weak_proxy
) forms reuse the same
facade definitions; algorithms can negotiate the weakest acceptable
capability via substitution.Operation surface: - Virtual dispatch is member-function-centric; free functions and many operators typically need auxiliary patterns (ADL helpers, CRTP, friend thunks, or visitors). - Facades enumerate conventions regardless of whether they correspond to member, free, operator, or conversion forms; accessors keep call syntax natural.
Summary:
Concern
|
Virtual dispatch
|
This proposal
|
---|---|---|
Intrusiveness | Requires base class & altered layout | Works with existing, non-hierarchical types |
Surface extensibility | Fixed at definition | Post-hoc facade composition |
Ownership spectrum | External smart pointers | Integrated owning / observing / weak forms |
Layout impact | vptr per object | Object layout unchanged |
Heterogeneous storage | Additional wrappers | Any pointer-like witness (constraints permitting) |
Null handling | Convention-dependent | Uniform null propagation |
Key dynamic call path differences versus virtual dispatch:
proxy
can embed one (or a small
fixed number of) function pointers directly in the
proxy
object. The call site then
performs a single indirect branch (no preceding metadata pointer load).
A virtual call must always (a) load the vptr then (b) load the slot
before branching.proxy
holds a pointer to a dispatch
table. The call path becomes one metadata load + one branch (still one
fewer dependent load than the typical virtual sequence on the
architectures examined).proxy
itself and can load the
underlying object pointer lazily. This removes instructions at each call
site; the cost (a load of the stored witness) migrates into the dispatch
stub, which may be inlined or shared across call sites.Trade-offs: Embedding shrinks per-call instruction count but
increases proxy
size if many
conventions are inlined; implementations typically switch to a table
form after a small threshold. Late witness dereference slightly
lengthens the dispatch stub (it must recover the implementation pointer)
but keeps every call site minimal.
Sequence diagrams for a single convention
draw()
:
Embedded metadata (0 loads at call site):
sequenceDiagram participant C as Caller participant P as Proxy (embedded) participant S as Stub participant I as Impl C->>P: call draw() P-->>S: fn ptr (field) C->>S: indirect branch S->>I: load witness & invoke
Table metadata (1 load + branch):
sequenceDiagram participant C as Caller participant P as Proxy (table) participant M as Table participant I as Impl C->>P: call draw() P->>M: load entry M-->>I: fn ptr C->>I: indirect branch
Virtual dispatch (2 loads + branch):
sequenceDiagram participant C as Caller participant O as Object participant VT as VTable C->>O: call draw() O->>VT: load vptr VT-->>O: load slot C->>O: indirect branch
Observed effects in tight polymorphic loops (microbenchmarks and production traces):
Potential downsides / neutrality points:
proxy
size; implementations tune
thresholds empirically.We revalidated the call path analysis with a minimal experiment: a
facade containing a single const, non-throwing void member-like
operation on a trivially movable implementation type (two integers plus
padding), compared against a classic abstract base with one virtual
function and a similarly sized derived type. Two functions perform one
dynamic call each: one through an owning
proxy
, one through std::unique_ptr<Base>
.
Both allocate the concrete object dynamically; the
proxy
form populates embedded or
table metadata depending on configuration.
Clang 21.1.0 (-O3 -std=c++20 -DNDEBUG
)
generated on three architectures:
Pattern
|
x86‑64
|
AArch64
|
RV64
|
---|---|---|---|
Proxy call site | jmp [rdi+8] |
ldr x1, [x0,#8] br x1 |
ld a5,8(a0) jr a5 |
Virtual call site | mov rdi,[rdi] mov rax,[rdi] jmp [rax] |
ldr x0,[x0] ldr x8,[x0] ldr x1,[x8] br x1 |
ld a0,0(a0) ld a1,0(a0) ld a5,0(a1) jr a5 |
The embedded-metadata configuration reduces the call site to a single indirect branch. When more entries require a table pointer, the observed forms are:
Strategy
|
x86‑64 (illustrative)
|
Loads before branch
|
---|---|---|
Embedded function pointer | jmp [rdi+8] |
0 |
Table pointer in proxy | mov rax,[rdi] jmp [rax+offset]
(or equivalent) |
1 |
Virtual dispatch | mov rax,[rdi] mov rax,[rax] jmp [rax+slot] |
2 |
Construction code generation shows the proxy populating function pointer fields directly, while the virtual path writes a vptr into the newly allocated object. The proxy’s flexibility appears in that the stored witness could instead be:
Representative microbenchmark summary (values are baseline time
divided by proxy
time; >1 means
proxy
faster):
Scenario (median)
|
MSVC x64
|
GCC x64
|
Clang x64
|
Apple Clang ARM64
|
---|---|---|---|---|
Indirect call (small object) | 3.6× | 1.45× | 1.7× | ~1.0× |
Indirect call (large object) | 2.9× | 1.16× | 1.17× | 1.1× |
Lifetime vs unique_ptr
(small) |
5.7× | 5.1× | 5.3× | 4.4× |
Lifetime vs shared_ptr (no
pool) |
7.4× | 6.1× | 5.9× | 5.8× |
Representative; full data including variance, allocator sensitivity, and alternative metadata packing strategies remain in the public benchmark suite and the blog post: Analyzing the Performance of the “Proxy” Library.
Interpretation (representative, not normative):
proxy
allows small buffer
optimization (inline storage inside a pointer-like witness) while
keeping the same polymorphic surface; trivial relocation further helps
but is secondary.proxy
path is faster or
equal, and users can tune performance by adjusting facade parameters
(max size, alignment, copyability/relocatability/destrctibility
constraint levels).Cost considerations / neutrality:
Across the shown targets the
proxy
path often removes one or two
dependent pointer dereferences per dispatch relative to the minimal
virtual pattern (for these layout strategies). Eliminating even a single
repeated load in a hot indirect call site compounds: tighter critical
path, smaller instruction footprint, and reduced I-cache pressure.
Effects vary with architecture and workload; compute-bound bodies may
mask differences.
proxy<F>
may store any pointer-like witness object (raw pointer, custom smart
pointer, allocator / arena handle, offset pointer, tagged pointer, small
in-place object via helper facilities, etc.) that satisfies the facade’s
constraint levels and implements the required conventions / reflections.
The standard proposal scopes only the core handle; helper creation
functions (e.g. make_proxy
,
allocate_proxy
,
make_proxy_inplace
,
make_proxy_shared
) exist in the
deployed library but are not proposed here.proxy_view<F>
= proxy<observer_facade<F>>
;
conventions requiring ownership are removed; substitution to a view
never strengthens ownership.weak_proxy<F>
= proxy<weak_facade<F>>
;
adds lock()
returning proxy<F>
;
substitution rewrites return types to weak forms to avoid accidental
strengthening.substitution_dispatch
ensures null
propagation and enables trivial relocation semantics.The facility yields a coherent pointer‑centric model that:
The proposal adds a cohesive set of facilities to <memory>
;
no core language changes are required. Only the components listed above
(and the two adaptors demonstrated as essential) are in scope. A feature
test macro __cpp_lib_proxy
gates
adoption.
Interoperability: existing facilities (std::function
,
std::any
,
polymorphic allocators, sender/receiver, ranges) can layer over
proxy
by defining facades matching
their operational subsets, avoiding duplicated type erasure machinery.
Implementations that already ship a similar abstraction can provide a
thin compatibility facade that forwards to their internal representation
(or vice versa) using ordinary headers; no special inline-namespace
remapping is required beyond the usual vendor versioning practices.
Freestanding friendliness and header‑only structure permit incremental vendor adoption. The type‑theoretic framing addresses prior teachability concerns; the performance and code generation evidence addresses cost concerns. The design is intentionally minimal yet extensible.
This section collects the highest impact design drivers first, then details secondary but still relevant considerations. The overarching theme: maximize runtime performance and flexibility by keeping the polymorphic abstraction strictly in the library domain and aligned with ordinary pointer semantics.
The single most important decision is to ground the abstraction in
pointer semantics rather than object layout changes (vptr
injection) or mandatory heap indirection. A proxy<F>
holds a pointer-like witness plus facade metadata; the
underlying concrete object remains layout- and ABI-stable. This
decoupling yields several concrete benefits:
max_size
,
max_align
, constraint levels for
copy / relocation / destruction). Implementations can select direct
embedding, an SBO buffer, or a raw pointer with no semantic
ambiguity.Performance concerns (predictable indirection depth, instruction footprint, cache behavior) are dominant here; expressivity follows, not precedes, the pointer-centric model.
“Proxy” is a familiar word in multiple programming domains describing
an intermediary that forwards or mediates operations: JavaScript’s
Proxy
object (ECMAScript 6), dynamic
proxies in Java / C#, remote proxies in distributed systems, and classic
design patterns. Despite association with networking, the core semantics
(an interposed representative controlling access or behavior) match this
facility precisely: a proxy<F>
represents a concrete object via pointer indirection while
arbitrating the allowable operations declared by the facade.
Alternative names (“erasure”, “handle”, “witness”, “facade_handle”) were considered but rejected for one or more reasons:
“proxy” therefore offers: (1) cross-language precedent for
indirection with behavioral control, (2) teachability (developers
already explain design pattern proxies), (3) brevity and suitability as
a template identifier, (4) alignment with the extensions
(weak_proxy
,
proxy_view
) that remain semantically
clear.
Choosing a library design permits immediate deployment, experimentation, and composition without committing to new core-language constructs or syntax. Specific rationales:
Importantly, none of the performance wins depend on privileged compiler knowledge; they arise from deliberate layout and dispatch design expressible in portable C++.
A facade is a compile-time algebraic description of: (a) a
set of conventions (dispatchable operation families), (b) optional
reflection hooks, (c) storage constraint parameters. One can manually
compose capability sets by defining a new facade type whose
convention_types
/
reflection_types
tuples include (or
re-express) those of earlier facades and by tightening constraint
constants where desired. (Out-of-scope helper builders in the existing
implementation automate this aggregation, but the core model does not
depend on them.) Substitution is declared via a direct
convention whose dispatch type is
substitution_dispatch
producing
another proxy<G>
(or view / weak variants under adaptation).
Facade anatomy and relationships:
flowchart LR subgraph F[Facade] Cs[convention_types: tuple-like] Rs[reflection_types: tuple-like] MaxSize[max_size: size_t] MaxAlign[max_align: size_t] Copyability[copyability: constraint_level] Relocatability[relocatability: constraint_level] Destructibility[destructibility: constraint_level] end subgraph C1[Convention A] C1IsDirect[is_direct: bool] subgraph C1D[dispatch_type] C1A["accessor (optional)"] end C1Os[overload_types: tuple-like] end subgraph R1[Reflection A] R1IsDirect[is_direct: bool] subgraph R1R[reflector_type] R1A["accessor (optional)"] end end Cs --> C1 C1Os --> C1O1[Overload A] C1Os --> C1O2[Overload B] Cs --> C2[Convention B] Rs --> R1 Rs --> R2[Reflection B]
Key properties:
Performance is not an afterthought: the call path structure, trivial relocation, metadata embedding threshold, and constraint level encoding were all chosen to minimize instruction count, dependent loads, and code size in hot polymorphic loops. This reiterates and deepens Motivation: pointer semantics enable these wins by keeping the underlying object opaque yet layout-neutral.
Committee review often asks whether speedups hold in real code. One
large reported migration (approx. 120k lines) from pure vtable
interfaces (COM-style) to proxy
observed 8x-10x improvements in certain scenarios. While part of the
gain came from fixing prior design issues (e.g. overuse of std::dynamic_pointer_cast
),
a good abstraction guides better performance by making
inefficiencies (extra indirections, unnecessary ownership) explicit and
cheap to remove.
The proposal intentionally standardizes only the minimal substrate:
proxy
,
facade
(and supporting concepts),
substitution_dispatch
, the view and
weak adaptors, core named requirements, and invoke / reflect free
functions. The shipped implementation includes additional helpers
(factory functions, facade-construction utilities, skill sets,
formatting, RTTI expansions). These are deferred not due to
immaturity, but to keep initial wording review tractable and focused on
the fundamental model. Successful adoption of this subset unblocks
follow-on proposals that can add low-boilerplate facade authoring
facilities if the committee desires.
Runtime polymorphism is often required in constrained and embedded
environments that disallow large library dependencies or
RTTI/exception-heavy mechanisms. Every facility here is implementable in
a freestanding environment (the reference implementation already
compiles freestanding). Decoupling from heavy runtime features increases
likelihood of adoption in kernels, drivers, and accelerators where
pointer-size-optimized proxy_view
(observer variant) is valuable.
Authoring a facade in the minimal subset requires spelling out the convention and reflection tuples and the constraint constants explicitly. This is acceptable for an initial standard surface because:
Illustrative contrast (helper utility OUT OF SCOPE vs manual definition in this paper’s model):
// Hypothetical helper-based form (non-normative here)
struct Streamable : pro::facade_builder
::add_convention<pro::operator_dispatch<<"<<", true>, std::ostream&(std::ostream& out) const>
::build {};
// Manual minimal form (in-scope pattern)
struct StreamOutDispatch {
template <class T>
::ostream& operator()(const T& self, std::ostream& out) const {
stdreturn out << self;
}
template <class P, class D, class... Os> struct accessor; // primary
template <class P, class D>
struct accessor<P, D, std::ostream&(std::ostream&) const> {
friend std::ostream& operator<<(std::ostream& out, const P& self) {
return std::proxy_invoke<D, std::ostream&(std::ostream&) const>(self, out);
}
};
};
struct StreamOutConvention {
using dispatch_type = StreamOutDispatch;
using overload_types = std::tuple<std::ostream&(std::ostream&) const>;
static constexpr bool is_direct = false;
};
struct Streamable {
using convention_types = std::tuple<StreamOutConvention>;
using reflection_types = std::tuple<>;
static constexpr std::size_t max_size = 2 * sizeof(void*);
static constexpr std::size_t max_align = alignof(void*);
static constexpr auto copyability = std::constraint_level::none;
static constexpr auto relocatability = std::constraint_level::trivial;
static constexpr auto destructibility = std::constraint_level::nothrow;
};
The manual definition is verbose but straightforward, and serves to validate the facade abstraction before proposing ergonomic helpers.
constraint_level
values in the
facade define minimum lifetime operation guarantees. They make
optimization conditions statically checkable (e.g., enabling
trivial relocation paths) and keep the metafunction surface small
(single enum rather than a matrix of traits). They also allow users to
reason about ABI and object representation when choosing between
embedding and indirection.
substitution_dispatch
centralizes
conversion logic between facades. Advantages of expressing substitution
as a dispatch:
proxy
class template.Manual composition of operation sets (by forming a new
convention_types
tuple that lists
multiple convention types) enables post-hoc capability aggregation
uncommon in inheritance hierarchies where base ordering and diamond
rules complicate extension.
proxy_view
(observer_facade
) and
weak_proxy
(weak_facade
) share the same
operation vocabulary while reducing or altering ownership semantics:
lock()
convention and rewrite substitution returns to preserve weakness; this
avoids surprising ownership escalation in generic algorithms. Both
adaptors rely on the pointer semantic foundation (lifetime never
conflated with operation set).Concept-based named requirements generate precise
substitution failure messages (missing convention type, incorrect
dispatch, constraint level mismatch) that practitioners have reported as
shorter and more actionable than errors from monolithic type-erasure
wrappers. While proxy
is
template-heavy, mitigations exist and have been validated:
Empirically, compile speed has not been a blocker in production deployments (including large internal codebases and Windows components). Therefore compile-time cost should not impede standardization.
The current paper keeps the reflection surface minimal but
seated within the facade model (reflection types in
reflection_types
). This design
allows later reflection-centric extensions (introspection, tracing,
generic instrumentation) without redesigning or versioning the core
runtime handle.
Aspect
|
Benefit
|
Trade-off / Mitigation
|
---|---|---|
Pointer semantics | Decoupled lifetime, optimal call path | Requires facade boilerplate (future automation) |
Facade composition | Post-hoc capability assembly | More template instantiations (modules) |
Substitution as dispatch | Extensible, uniform syntax | Slight indirection for conversion (amortized) |
Explicit constraint levels | Static optimization triggers | Author must choose levels (defaults / guidance) |
View & weak adaptors | Unified API across ownership modes | Additional conceptual surface (documented) |
Library (not language) | Deployable now, evolvable | No new syntax sugar yet (could follow) |
Template-heavy design | Zero overhead abstractions | Compilation cost (managed) |
Overall, the design prioritizes predictable high-performance polymorphism under explicit user control, deferring non-essential ergonomic helpers until after the foundational model is standardized.
In [version.syn], add:
#define __cpp_lib_proxy YYYYMML // also in <memory>
The placeholder value shall be adjusted to denote this proposal’s date of adoption.
The following named requirements are introduced. The exposition-only entities mentioned herein have no linkage and are used solely to define requirements.
A type O
meets the
ProOverload requirements if, for any facade type
F
that meets
ProBasicFacade, the exposition-only type substituted-overload<O, F>
is defined as:
OT<F>
if O
is a specialization of facade_aware_overload_t<OT>
;O
.substituted-overload<O, F>
shall denote one of the following function types where
R
is a return type and
Args...
are
zero or more parameter types:
(Args...)
R(Args...) noexcept
R(Args...) &
R(Args...) & noexcept
R(Args...) &&
R(Args...) && noexcept
R(Args...) const
R(Args...) const noexcept
R(Args...) const&
R(Args...) const& noexcept
R(Args...) const&&
R(Args...) const&& noexcept R
Given a type D
, a type
T
, and a type
O
meeting ProOverload, let
R
be the return type and
Args...
the
parameter types of O
. A type
D
meets the ProDispatch
requirements of T
and
O
if:
D()
is
well-formed, creates a value of type
D
, and does not throw.O
, the invocation expression formed
as specified below is well-formed and has the semantics indicated.
args...
denotes a pack of glvalues of types (possibly cv-qualified)
Args...
where each args_i
is passed as std::forward<Args_i>(args_i)
.Invocation forms (let v
be an
lvalue of type T
,
cv
a
const T
lvalue):
Overload form
|
Expression
|
Semantics
|
---|---|---|
R(Args...) |
INVOKE<R>(D(), v, std::forward<Args>(args)...) |
Invokes with v ; may throw. |
R(Args...) noexcept |
same | Shall not throw. |
R(Args...) & |
same | May throw. |
R(Args...) & noexcept |
same | Shall not throw. |
R(Args...) && |
INVOKE<R>(D(), std::move(v), std::forward<Args>(args)...) |
May throw. |
R(Args...) && noexcept |
same | Shall not throw. |
R(Args...) const |
INVOKE<R>(D(), cv, std::forward<Args>(args)...) |
May throw. |
R(Args...) const noexcept |
same | Shall not throw. |
R(Args...) const& |
same as
const
form |
May throw. |
R(Args...) const& noexcept |
same as const noexcept
form |
Shall not throw. |
R(Args...) const&& |
INVOKE<R>(D(), std::move(cv), std::forward<Args>(args)...) |
May throw. |
R(Args...) const&& noexcept |
same | Shall not throw. |
A type C
meets the
ProBasicConvention requirements if:
C::is_direct
is a core constant expression of type
bool
.typename C::dispatch_type
is a trivial type.typename C::overload_types
is a tuple-like type whose element types are a non-empty set of distinct
types each meeting ProOverload.A type C
meets the
ProConvention requirements of a type
P
if
C
meets ProBasicConvention
and for each overload type O
in
typename C::overload_types
:
C::is_direct
is true
,
typename C::dispatch_type
meets ProDispatch of P
and
O
.C::is_direct == false
):
let QP
be
P
with the cv and ref qualifiers of
O
(lvalue reference if
O
has no ref qualifier). The
expression *std::forward<QP>(qp)
shall be well-formed for an lvalue
qp
of type
QP
; and typename C::dispatch_type
meets ProDispatch of decltype(*std::forward<QP>(qp))
and O
.A type R
meets the
ProBasicReflection requirements if:
R::is_direct
is a core constant expression of type
bool
.typename R::reflector_type
is a trivial type that defines a data structure describing reflected
metadata.A type R
meets the
ProReflection requirements of a type
P
if
R
meets ProBasicReflection
and the expression typename R::reflector_type(std::in_place_type<T>)
is a core constant expression that constructs a typename R::reflector_type
,
where T
is
P
if
R::is_direct
is true
,
otherwise typename std::pointer_traits<P>::element_type
.
A type F
meets the
ProBasicFacade requirements if:
typename F::convention_types
is a tuple-like type containing zero or more distinct types each meeting
ProBasicConvention.typename F::reflection_types
is a tuple-like type containing zero or more distinct types each
defining a reflection data structure.F::max_size
,
F::max_align
are core constant expressions of type
size_t
.F::copyability
,
F::relocatability
,
F::destructibility
are core constant expressions of type
constraint_level
.A type F
meets the
ProFacade requirements of a type
P
if
F
meets ProBasicFacade
and:
typename F::convention_types
meets ProConvention of
P
.typename F::reflection_types
meets ProReflection of
P
.F::max_size >= sizeof(P)
and F::max_align >= alignof(P)
.P
:
none
imposes no
requirement; nontrivial
requires
is_copy_constructible_v<P>
;
nothrow
additionally requires the
copy construction is non-throwing;
trivial
requires is_trivially_copy_constructible_v<P>
.trivial
indicates trivial
relocatability.A type T
meets the
ProAccessible requirements of types
Args...
if
typename T::template accessor<Args...>
is a nothrow-default-constructible, trivially copyable,
non-final
type to inject invocation or reflection functions into
proxy
or
proxy_indirect_accessor
specializations.
The synopsis below shows the new and updated declarations.
namespace std {
enum class constraint_level { none, nontrivial, nothrow, trivial };
template <template <class> class O>
struct facade_aware_overload_t {
() = delete;
facade_aware_overload_t};
template <class F>
concept facade = see below;
template <class P, class F>
concept proxiable = see below;
template <facade F>
class proxy_indirect_accessor;
template <facade F>
class proxy;
template <class D, class O, facade F, class... Args>
(proxy_indirect_accessor<F>& p, Args&&... args);
see below proxy_invoke
template <class D, class O, facade F, class... Args>
(const proxy_indirect_accessor<F>& p, Args&&... args);
see below proxy_invoke
template <class D, class O, facade F, class... Args>
(proxy_indirect_accessor<F>&& p, Args&&... args);
see below proxy_invoke
template <class D, class O, facade F, class... Args>
(const proxy_indirect_accessor<F>&& p, Args&&... args);
see below proxy_invoke
template <class D, class O, facade F, class... Args>
(proxy<F>& p, Args&&... args);
see below proxy_invoke
template <class D, class O, facade F, class... Args>
(const proxy<F>& p, Args&&... args);
see below proxy_invoke
template <class D, class O, facade F, class... Args>
(proxy<F>&& p, Args&&... args);
see below proxy_invoke
template <class D, class O, facade F, class... Args>
(const proxy<F>&& p, Args&&... args);
see below proxy_invoke
template <class R, facade F>
const R& proxy_reflect(const proxy_indirect_accessor<F>& p) noexcept;
template <class R, facade F>
const R& proxy_reflect(const proxy<F>& p) noexcept;
struct substitution_dispatch;
template <facade F>
struct observer_facade;
template <facade F>
using proxy_view = proxy<observer_facade<F>>;
template <facade F>
struct weak_facade;
template <facade F>
using weak_proxy = proxy<weak_facade<F>>;
} // namespace std
All declarations are in namespace
std
.
constraint_level
enum class constraint_level { none, nontrivial, nothrow, trivial };
For a lifetime operation (copy construction, relocation, or destruction) designated by context:
none
: no requirements.nontrivial
: the operation is
supported.nothrow
: the operation is
supported and is non-throwing.trivial
: the operation is
trivial.facade
template<class F> concept facade = see below;
facade<F>
is satisfied if and only if F
meets
the ProBasicFacade requirements.
proxiable
template<class P, class F> concept proxiable = see below;
proxiable<P, F>
is satisfied if and only if F
meets
ProFacade of P
. Evaluating
proxiable<P, F>
where P
is an incomplete type whose
completion could change the result is undefined behavior.
proxy_indirect_accessor
template<facade F> class proxy_indirect_accessor;
proxy_indirect_accessor<F>
is an indirection helper providing accessors for the indirect
conventions and reflections of F
(those whose is_direct
member is
false
). It
has no default, copy, or move constructors. For each convention type
C
in F::convention_types
with C::is_direct == false
,
if C::dispatch_type
meets ProAccessible of proxy_indirect_accessor<F>, C::dispatch_type, substituted-overload-types...
,
proxy_indirect_accessor<F>
inherits C::dispatch_type::accessor<proxy_indirect_accessor<F>, C::dispatch_type, substituted-overload-types...>
.
For each reflection type R
in F::reflection_types
with R::is_direct == false
and whose reflector_type
meets
ProAccessible of proxy_indirect_accessor<F>, R::reflector_type
,
it inherits R::reflector_type::accessor<proxy_indirect_accessor<F>, R::reflector_type>
.
proxy
template<facade F> class proxy;
A proxy<F>
object either contains a value or does not contain a value. If it
contains a value the contained type
P
is a pointer type such that proxiable<P, F>
is true
. The
value of type P
is stored within the
proxy
object; construction of
P
may perform dynamic allocation per
P
’s semantics, but the
proxy
does not allocate storage for
P
.
Let Cs
be the element types of
F::convention_types
and Rs
the element types of F::reflection_types
.
For each convention type C
in
Cs
with C::is_direct == true
,
if C::dispatch_type
meets ProAccessible of proxy<F>, C::dispatch_type, substituted-overload-types...
,
proxy<F>
inherits the corresponding accessor. For each reflection type
R
in
Rs
with R::is_direct == true
whose reflector_type
meets
ProAccessible of proxy<F>, R::reflector_type
,
proxy<F>
inherits the corresponding accessor.
template <facade F>
class proxy {
public:
using facade_type = F;
() noexcept { initialize(); }
proxy(std::nullptr_t) noexcept : proxy() {}
proxy(const proxy&) noexcept
proxyrequires(see below)
= default;
(const proxy& rhs) noexcept(see below)
proxyrequires(see below);
(proxy&& rhs) noexcept(see below)
proxyrequires(see below);
template <class P>
constexpr proxy(P&& ptr) noexcept(see below)
requires(see below);
template <class P, class... Args>
constexpr explicit proxy(std::in_place_type_t<P>, Args&&... args) noexcept(see below)
requires(see below);
template <class P, class U, class... Args>
constexpr explicit proxy(
::in_place_type_t<P>, std::initializer_list<U> il,
std&&... args) noexcept(see below)
Argsrequires(see below);
& operator=(std::nullptr_t) noexcept(see below)
proxyrequires(see below);
& operator=(const proxy&) noexcept requires(see below) = default;
proxy& operator=(const proxy& rhs) noexcept(see below)
proxyrequires(see below);
& operator=(proxy&& rhs) noexcept(see below)
proxyrequires(see below);
template <class P>
constexpr proxy& operator=(P&& ptr) noexcept(see below)
requires(see below);
~proxy() requires(see below) = default;
~proxy() noexcept(see below)
requires(see below);
bool has_value() const noexcept;
explicit operator bool() const noexcept;
void reset() noexcept(see below)
requires(see below);
void swap(proxy& rhs) noexcept(see below)
requires(see below);
template <class P, class... Args>
constexpr P& emplace(Args&&... args) noexcept(see below) requires(see below);
template <class P, class U, class... Args>
constexpr P& emplace(std::initializer_list<U> il, Args&&... args) noexcept(see below) requires(see below);
<F>* operator->() noexcept;
proxy_indirect_accessorconst proxy_indirect_accessor<F>* operator->() const noexcept;
<F>& operator*() & noexcept;
proxy_indirect_accessorconst proxy_indirect_accessor<F>& operator*() const& noexcept;
<F>&& operator*() && noexcept;
proxy_indirect_accessorconst proxy_indirect_accessor<F>&& operator*() const&& noexcept;
friend void swap(proxy& lhs, proxy& rhs) noexcept(noexcept(lhs.swap(rhs)));
friend bool operator==(const proxy& lhs, std::nullptr_t) noexcept;
};
proxy() noexcept;
proxy(nullptr_t) noexcept;
Effects: Constructs an object that does not contain a value.
proxy(const proxy&) noexcept
is defined as deleted unless F::copyability == constraint_level::trivial
,
in which case it is trivial and performs a bitwise copy.
proxy(const proxy& rhs) noexcept(F::copyability == constraint_level::nothrow)
is declared when F::copyability
is
nontrivial
or
nothrow
. Effects: if
rhs
contains a value of type
P
, initializes the contained value
of the constructed object with a copy of
rhs
’s contained value; otherwise
constructs an empty proxy
.
proxy(proxy&& rhs) noexcept(F::relocatability == constraint_level::nothrow)
is declared when F::relocatability >= constraint_level::nontrivial
and F::copyability != constraint_level::trivial
.
Effects: if rhs
contains a value,
moves its contained value into the constructed object (using either a
move construction of P
or an
implementation-defined trivial relocation when permitted by the
constraint levels); otherwise constructs an empty
proxy
. Postconditions:
rhs
does not contain a value.
template<class P> explicit(false) proxy(P&& ptr) noexcept(is_nothrow_constructible_v<decay_t<P>, P>)
participates in overload resolution only if decay_t<P>
is not a specialization of proxy
and
is not a specialization of
in_place_type_t
. Requires: is_constructible_v<decay_t<P>, P>
and proxiable<decay_t<P>, F>
.
Effects: constructs a contained value of type decay_t<P>
initialized with std::forward<P>(ptr)
.
template<class P, class... Args> explicit proxy(in_place_type_t<P>, Args&&... args)
template<class P, class U, class... Args> explicit proxy(in_place_type_t<P>, initializer_list<U> il, Args&&... args)
Require: the selected constructor of
P
is well-formed and proxiable<P, F>
is true
.
Effects: constructs a contained value of type
P
direct-non-list-initialized with
the arguments.
~proxy();
Effects: If the object contains a value of type
P
, destroys that value. The
operation is conditionally trivial and/or noexcept according to F::destructibility
and F::relocatability
.
Copy/move assignment operators are provided analogously to the constructors, subject to the same constraint levels; detailed wording is omitted for brevity and follows existing patterns for conditionally trivial wrappers.
explicit operator bool() const noexcept;
bool has_value() const noexcept;
Returns:
true
if the
object contains a value, otherwise
false
.
operator->
and operator*
)proxy_indirect_accessor<F>* operator->() noexcept;
const proxy_indirect_accessor<F>* operator->() const noexcept;
proxy_indirect_accessor<F>& operator*() & noexcept;
const proxy_indirect_accessor<F>& operator*() const& noexcept;
proxy_indirect_accessor<F>&& operator*() && noexcept;
const proxy_indirect_accessor<F>&& operator*() const&& noexcept;
Returns: operator->()
returns a pointer to, and operator*()
returns a (possibly cv/ref-qualified) reference or xvalue reference to,
the proxy_indirect_accessor<F>
subobject that provides accessors for all indirect conventions and
reflections (those with is_direct == false
).
Remarks: The behavior is undefined if !has_value()
.
Notes: No value-presence check is performed; programs can test
has_value()
or compare with
nullptr
prior to calling these operators.
void reset() noexcept;
Effects: if the object contains a value, destroys the value; afterwards the object does not contain a value.
template<class P, class... Args> P& emplace(Args&&... args);
template<class P, class U, class... Args> P& emplace(initializer_list<U> il, Args&&... args);
Requires: proxiable<P, F>
is true
.
Effects: destroys any contained value, then constructs a contained value
of type P
direct-non-list-initialized from the arguments. Returns: a reference to
the new contained value.
friend bool operator==(const proxy& p, nullptr_t) noexcept;
Returns: !p.has_value()
.
friend void swap(proxy& a, proxy& b) noexcept(/* see below */);
Effects: Exchanges the contained values (including emptiness). The
exception specification is
noexcept
if
relocating or copying the contained pointer types as required is
non-throwing under the constraint levels.
proxy_invoke
For a function template specialization proxy_invoke<D, O, F>(ref, args...)
,
let O
be a type meeting
ProOverload and D
a type
meeting ProDispatch of the relevant target type and
O
.
Overloads taking proxy_indirect_accessor<F>
:
Let ptr
be the contained pointer of
the associated proxy<F>
object with the same cv/ref qualification as the parameter
ref
. Effects: return INVOKE<R>(D(), *ptr, static_cast<Args2>(args)...)
where
Args2...
and
R
are the parameter and return types
of O
.
Overloads taking proxy<F>
:
Requires: the object contains a value. Effects: return INVOKE<R>(D(), ptr, static_cast<Args2>(args)...)
where ptr
is the contained
value.
proxy_reflect
template<class R, facade F> const R& proxy_reflect(const proxy_indirect_accessor<F>& p) noexcept;
Effects: Returns a const R&
direct-non-list-initialized from in_place_type<T>
where T
is the pointee type of the
contained pointer of the associated proxy<F>
object.
template<class R, facade F> const R& proxy_reflect(const proxy<F>& p) noexcept;
Requires: p.has_value()
.
Effects: Returns a const R&
direct-non-list-initialized from in_place_type<P>
where P
is the contained pointer
type.
The reference may be invalidated if the
proxy
is subsequently modified.
substitution_dispatch
struct substitution_dispatch { substitution_dispatch() noexcept; }
template<class T> T&& operator()(T&& value) const noexcept;
Effects: Returns std::forward<T>(value)
.
Remarks: If T
is an unqualified
object type and an rvalue and the target facade substitution produces a
proxy
instantiation whose contained
type is trivially relocatable, the implementation may relocate the
storage bitwise when initializing the result proxy instead of invoking a
move constructor.
substitution_dispatch::accessor
template<class P, class D, class... Os> struct accessor;
The nested class template substitution_dispatch::accessor
participates in satisfying the ProAccessible requirements for
direct conventions whose
dispatch_type
is
substitution_dispatch
and whose
overload types return proxy<G>
(or adapted forms in view / weak facades).
Os...
, the
primary template accessor<P, D, Os...>
is not constructible.Os...
with
sizeof...(Os) > 1
where each accessor<P, D, O>
(with O
a single element of
Os...
) is
nothrow-default-initializable, the partial specializationtemplate<class P, class D, class... Os>
requires(sizeof...(Os) > 1 && (std::is_constructible_v<accessor<P, D, Os>> && ...))
struct accessor<P, D, Os...> : accessor<P, D, Os>... { using accessor<P, D, Os>::operator return-type-of<Os>...; };
inherits all base accessor<P, D, O>
specializations and brings their conversion operators into scope via
using
-declarations.
Here return-type-of<O>
denotes the return type of the overload type
O
. 3. For an overload type
O
of the form proxy<F>() cv ref noex
(for some facade F
and
cv/ref/noexcept qualifiers), the partial specialization
template<class P, class D, facade F>
struct accessor<P, D, proxy<F>() cv ref noex> { operator proxy<F>() cv ref noex; };
provides an implicit conversion function operator proxy<F>() cv ref noex
.
Effects: Let self
denote the
proxy<F>
object that contains the accessor
base subobject, with the same cv/ref qualifiers as the operator being
formed. If self.has_value()
is false
,
returns an empty proxy<F>
.
Otherwise, returns the result of proxy_invoke<substitution_dispatch, proxy<F>() cv ref noex>(self)
(where the indicated overload type is used to select the substitution
dispatch). The returned proxy contains a value substituting the
underlying object; null propagation is thus guaranteed.
Remarks: Implementations may perform trivial relocation when constructing the result as permitted by the relocatability constraint level of the target facade.
All members not explicitly specified are exposition-only and have no further observable effect.
observer_facade
and alias template
proxy_view
template<facade F> struct observer_facade;
For each convention C
in F::convention_types
:
- If C::is_direct == false
,
include C
unchanged in observer_facade<F>::convention_types
.
- Else if C::dispatch_type
is substitution_dispatch
, include a
transformed convention
C'
whose
overload set is identical to C
except each overload returning proxy<G>
is replaced with the same signature returning proxy_view<G>
preserving cv/ref qualifiers and
noexcept
. -
Otherwise discard C
.
For each reflection type R
in
F::reflection_types
,
include R
only if R::is_direct == false
.
observer_facade<F>::max_size == sizeof(void*)
,
max_align == alignof(void*)
,
and the constraint levels are all constraint_level::trivial
.
template<facade F> using proxy_view = proxy<observer_facade<F>>;
weak_facade
and alias template
weak_proxy
template<facade F> struct weak_facade;
weak_facade<F>::convention_types
consists of: * A first direct convention providing an overload proxy<F>() const noexcept
named lock
whose dispatch type
models ProDispatch and whose invocation attempts to obtain a
strong proxy<F>
referencing the underlying object, returning an empty proxy if the
object has expired. * For each direct convention
C
of
F
whose
dispatch_type
is
substitution_dispatch
, a transformed
convention
C'
identical except that each overload returning proxy<G>
is replaced with an overload returning weak_proxy<G>
preserving qualifiers and
noexcept
.
No other conventions are included. weak_facade<F>::reflection_types
is empty. The constraint constants
(max_size
,
max_align
, and all three
constraint_level
members) are copied
from F
.
template<facade F> using weak_proxy = proxy<weak_facade<F>>;
For any proxy<F>
objects a
and
b
: a == nullptr
if and only if !a.has_value()
.
Overload resolution selects the friend defined above. swap(a, b)
exchanges their contained values (including emptiness). The complexity
is O(1)
.
Thanks to the WG21 members who reviewed earlier revisions, especially the participants in LEWGI who asked for a clearer explanation of the type theoretic structure behind proxy. Thanks to Tian Liao (Microsoft) for the insights into the library design and open source. Thanks to Wei Chen (Jilin University), Herb Sutter, Roger Orr, Chandler Carruth, Daveed Vandevoorde, Bjarne Stroustrup, JF Bastien, Bengt Gustafsson, Chuang Li (Microsoft), Nevin Liber and Billy Baker for their valuable feedback on earlier revisions of this paper.
proxy
is a proposed <memory>
facility for runtime polymorphism built purely on ordinary pointer
semantics: a proxy<F>
holds a pointer‑like witness plus facade metadata describing operations
(conventions), optional reflection, and storage / lifetime constraints.
Interfaces become post‑hoc, composable facades rather than baked‑in base
classes; concrete types stay unchanged.
Key points:
proxy
, observing
proxy_view
, and
weak_proxy
share the same callable
surface; substitution_dispatch
enables capability downgrades without reconstruction.In short, proxy
supplies a
compact, extensible, production‑proven substrate for runtime
polymorphism: it leaves object layouts untouched, unifies ownership
modes, enables post‑hoc capability composition and refinement, and
delivers measurable dispatch and lifetime efficiencies. It is a modern,
library‑only successor to traditional virtual functions.