proxy

Class template proxy

template <class F>
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, std::move_only_function, std::any, etc.), proxy is based on pointer semantics. It supports flexible lifetime management without runtime garbage collection (GC) at runtime, and offers best-in-class code generation quality, extendibility and accessibility.

To instantiate proxy<F>, F shall model concept facade. As per facade<F>, typename F::convention_types shall be a tuple-like type containing any number of distinct types Cs. For each type C in Cs, if C meets the ProAccessible requirements of F, typename C::template accessor<F> is inherited by proxy<F> when C::is_direct is true. Otherwise, it is inherited by the return type of operator* when C::is_direct is false. Implementation of accessors can call access_proxy to access the proxy object. It is recommended to use facade_builder to define a facade type.

Any instance of proxy<F> at any given point in time either contains a value or does not contain a value. If a proxy<F> contains a value, the type of the value shall be a pointer type P where proxiable<P, F> 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) constructs a proxy object
(destructor) destroys a proxy object
operator= assigns a proxy object
operator bool
has_value
checks if the proxy contains a value
reset destroys any contained value
swap exchanges the contents
emplace constructs the contained value in-place
operator->
operator*
accesses the accessors of the indirect conventions

Non-member functions

Name Description
swap overload the std::swap algorithm
operator== compares a proxy with nullptr

Comparing with Other Standard Polymorphic Wrappers

The C++ standard includes several polymorphic wrappers, such as std::function, std::packaged_task, std::any, and std::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 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.

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. It is recommended to use facade_builder to define a custom facade with any conventions, reflections, or constraints.

Example

#include <iostream>
#include <map>
#include <memory>
#include <string>
#include <vector>

#include "proxy.h"

PRO_DEF_MEM_DISPATCH(MemAt, at);

struct Dictionary : pro::facade_builder
    ::add_convention<MemAt, std::string(int)>
    ::build {};

// This is a function, rather than a function template
void PrintDictionary(pro::proxy<Dictionary> dictionary) {
  std::cout << dictionary->at(1) << "\n";
}

int main() {
  static std::map<int, std::string> container11;
  auto container2 = std::make_shared<std::vector<const char*>>();
  container2->push_back("hello");
  container2->push_back("world");
  PrintDictionary(&container1);  // Prints: "hello"
  PrintDictionary(container2);  // Prints: "world"
}

See Also