diff --git a/CMakeLists.txt b/CMakeLists.txt index 08aca3be..ee8d16c0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.28) -project(msft_proxy4 VERSION 4.0.0 LANGUAGES CXX) +project(msft_proxy4 VERSION 4.0.1 LANGUAGES CXX) add_library(msft_proxy4 INTERFACE) set_target_properties(msft_proxy4 PROPERTIES EXPORT_NAME proxy) add_library(msft_proxy4::proxy ALIAS msft_proxy4) diff --git a/README.md b/README.md index 798e31b4..9d55153d 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ Please refer to the [Proxy's Frequently Asked Questions](https://microsoft.githu ### Hello World -Let's get started with the following "Hello World" example ([run](https://godbolt.org/z/3G363xz71)): +Let's get started with the following "Hello World" example ([run](https://godbolt.org/z/f7W36f3se)): ```cpp #include @@ -61,7 +61,7 @@ struct Formattable : pro::facade_builder ::build {}; int main() { - std::string str = "Hello World"; + static std::string str = "Hello World"; pro::proxy p1 = &str; std::cout << std::format("*p1 = {}\n", *p1); // Prints "*p1 = Hello World" @@ -98,7 +98,7 @@ Note: If you prefer the library to be consumed as a (C++20) module, refer to [C+ ### Hello World (Stream Version) -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](https://godbolt.org/z/xcsM3v3cY)): +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](https://godbolt.org/z/447aMbrbj)): ```cpp #include @@ -112,7 +112,7 @@ struct Streamable : pro::facade_builder ::build {}; int main() { - std::string str = "Hello World"; + static std::string str = "Hello World"; pro::proxy p1 = &str; std::cout << "*p1 = " << *p1 << "\n"; // Prints "p1 = Hello World" @@ -254,6 +254,7 @@ ctest --test-dir build -j ## Related Resources +- August, 2025: [Announcing Proxy 4: The Next Leap in C++ Polymorphism](https://devblogs.microsoft.com/cppblog/announcing-proxy-4-the-next-leap-in-c-polymorphism/) - May, 2025: [Published ISO C++ proposal P3086R34 Proxy: A Pointer-Semantics-Based Polymorphism Library](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3086r4.pdf) - January, 2025: [Published ISO C++ proposal P3584R0: Enrich Facade Creation Facilities for the Pointer-Semantics-Based Polymorphism Library - Proxy](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3584r0.pdf) - November, 2024: [Analyzing the Performance of the “Proxy” Library](https://devblogs.microsoft.com/cppblog/analyzing-the-performance-of-the-proxy-library/) diff --git a/docs/spec/.pages b/docs/spec/.pages index 6225234c..2b6247cb 100644 --- a/docs/spec/.pages +++ b/docs/spec/.pages @@ -18,6 +18,7 @@ nav: - proxy_indirect_accessor: proxy_indirect_accessor.md - proxy_view
observer_facade: proxy_view.md - proxy: proxy + - substitution_dispatch: substitution_dispatch - weak_dispatch: weak_dispatch - weak_proxy
weak_facade: weak_proxy.md - Alias Templates: diff --git a/docs/spec/README.md b/docs/spec/README.md index 0fb5f2f3..255903e4 100644 --- a/docs/spec/README.md +++ b/docs/spec/README.md @@ -31,6 +31,7 @@ This document provides the API specifications for the C++ library Proxy (version | [`proxy_indirect_accessor`](proxy_indirect_accessor.md) | Provides indirection accessibility for `proxy` | | [`proxy_view`
`observer_facade`](proxy_view.md) | Non-owning `proxy` optimized for raw pointer types | | [`proxy`](proxy/README.md) | Wraps a pointer object matching specified facade | +| [`substitution_dispatch`](substitution_dispatch/README.md) | Dispatch type for `proxy` substitution with accessibility | | [`weak_dispatch`](weak_dispatch/README.md) | Weak dispatch type with a default implementation that throws `not_implemented` | | [`weak_proxy`
`weak_facade`](weak_proxy.md) | `proxy` with weak ownership | diff --git a/docs/spec/basic_facade_builder/add_facade.md b/docs/spec/basic_facade_builder/add_facade.md index 645cc53e..2d73b5aa 100644 --- a/docs/spec/basic_facade_builder/add_facade.md +++ b/docs/spec/basic_facade_builder/add_facade.md @@ -1,7 +1,7 @@ # `basic_facade_builder::add_facade` ```cpp -template +template using add_facade = basic_facade_builder; ``` @@ -14,11 +14,11 @@ The alias template `add_facade` of `basic_facade_builderemplace(456, "trivial"); - // Performs an upward conversion from an rvalue reference + // Performs a substitution from an rvalue reference pro::proxy p3 = std::move(p2); std::cout << p1->size() << "\n"; // Prints "1" std::cout << p1->at(123) << "\n"; // Prints "lalala" diff --git a/docs/spec/explicit_conversion_dispatch/accessor.md b/docs/spec/explicit_conversion_dispatch/accessor.md index 62d89be0..2bec72b3 100644 --- a/docs/spec/explicit_conversion_dispatch/accessor.md +++ b/docs/spec/explicit_conversion_dispatch/accessor.md @@ -15,7 +15,7 @@ struct accessor : accessor... { }; // (3) -template +template struct accessor { explicit operator T() cv ref noex; }; diff --git a/docs/spec/implicit_conversion_dispatch/accessor.md b/docs/spec/implicit_conversion_dispatch/accessor.md index a8e2f12b..cf871e51 100644 --- a/docs/spec/implicit_conversion_dispatch/accessor.md +++ b/docs/spec/implicit_conversion_dispatch/accessor.md @@ -15,7 +15,7 @@ struct accessor : accessor... { }; // (3) -template +template struct accessor { operator T() cv ref noex; }; diff --git a/docs/spec/msft_lib_proxy.md b/docs/spec/msft_lib_proxy.md index 0087a59d..b3c8593b 100644 --- a/docs/spec/msft_lib_proxy.md +++ b/docs/spec/msft_lib_proxy.md @@ -10,6 +10,7 @@ Starting with 3.0.0, Proxy ships a feature-test macro that encodes the library v | Version | Value of `__msft_lib_proxy` | | ------- | --------------------------- | +| 4.0.1 | `202510L` | | 4.0.0 | `202508L` | | 3.4.0 | `202505L` | | 3.3.0 | `202503L` | diff --git a/docs/spec/proxy_view.md b/docs/spec/proxy_view.md index 66da8c72..3fb1c5a0 100644 --- a/docs/spec/proxy_view.md +++ b/docs/spec/proxy_view.md @@ -13,14 +13,16 @@ template using proxy_view = proxy>; ``` -Class template `observer_facade` is a [facade](facade.md) type for observer pointers (e.g., raw pointers) potentially dereferenced from a `proxy` object. +`proxy_view` is a non-owning, trivially copyable, trivially relocatable view of an object that models [`proxiable_target`](proxiable_target.md). It behaves like a `proxy` except that it never owns the lifetime of the underlying object. + +`observer_facade` adapts an existing [facade](facade.md) `F` for this non-owning use. The adaptation preserves only those parts of `F` that remain semantically valid when the storage is reduced to a single pointer and modifies substitution conversions so that view-ness is preserved (substitution that would have produced an owning `proxy` instead produces a `proxy_view`). ## Member Types of `observer_facade` | Name | Description | | ------------------ | ------------------------------------------------------------ | -| `convention_types` | A [tuple-like](https://en.cppreference.com/w/cpp/utility/tuple/tuple-like) type transformed from `typename F::convention_types`. Specifically, for each convention type `C` defined in `typename F::convention_types`, `C` is reserved when `C::is_direct` is `false` or when `C` is an upward conversion convention added via [`basic_facade_builder::add_facade`](basic_facade_builder/add_facade.md), or otherwise filtered out. | -| `reflection_types` | A [tuple-like](https://en.cppreference.com/w/cpp/utility/tuple/tuple-like) type transformed from `typename F::reflection_types`. Specifically, for each reflection type `R` in `typename F::reflection_types`, `R` is reserved when `R::is_direct` is `false`, or otherwise filtered out. | +| `convention_types` | A [tuple-like](https://en.cppreference.com/w/cpp/utility/tuple/tuple-like) type transformed from `typename F::convention_types`. Specifically, for each convention `C` in `typename F::convention_types`:
- If `C::is_direct` is `false`, include `C` unchanged.
- Otherwise, if `typename C::dispatch_type` is [`substitution_dispatch`](./substitution_dispatch/README.md), include a transformed convention `C'` whose
* `is_direct` is `true` and `dispatch_type` is still `substitution_dispatch`.
* For every overload `O` in `typename C::overload_types` with signature (after cv/ref/noexcept qualifiers) returning a `proxy`, replace its return type with `proxy_view` while preserving qualifiers and `noexcept`.
- Otherwise `C` is discarded. | +| `reflection_types` | A [tuple-like](https://en.cppreference.com/w/cpp/utility/tuple/tuple-like) type transformed from `typename F::reflection_types`. Specifically, for each reflection type `R` in `typename F::reflection_types`, `R` is included when `R::is_direct` is `false`, or otherwise discarded. | ## Member Constants of `observer_facade` diff --git a/docs/spec/substitution_dispatch/.pages b/docs/spec/substitution_dispatch/.pages new file mode 100644 index 00000000..b1248442 --- /dev/null +++ b/docs/spec/substitution_dispatch/.pages @@ -0,0 +1,4 @@ +nav: + - substitution_dispatch: README.md + - accessor: accessor.md + - operator(): operator_call.md diff --git a/docs/spec/substitution_dispatch/README.md b/docs/spec/substitution_dispatch/README.md new file mode 100644 index 00000000..c54ba687 --- /dev/null +++ b/docs/spec/substitution_dispatch/README.md @@ -0,0 +1,71 @@ +# Class `substitution_dispatch` + +> Header: `proxy.h` +> Module: `proxy` +> Namespace: `pro::inline v4` +> Since: 4.0.1 + +```cpp +class substitution_dispatch; +``` + +Class `substitution_dispatch` models a [dispatch](../ProDispatch.md) type for `proxy` substitution. It meets the [*ProAccessible* requirements](../ProAccessible.md) of applicable types. + +## Member Functions + +| Name | Description | +| -------------------------------- | -------------------------------------------- | +| (constructor) [nothrow] | constructs an `substitution_dispatch` object | +| [`operator()`](operator_call.md) | invokes the dispatch | + +## Member Types + +| Name | Description | +| ------------------------- | --------------------------------- | +| [`accessor`](accessor.md) | provides accessibility to `proxy` | + +## Example + +```cpp +#include + +#include + +struct Runnable : pro::facade_builder // + ::add_convention, void()> // + ::build {}; + +struct CopyableRunnable : pro::facade_builder // + ::support_copy // + ::add_facade // + ::add_direct_convention() const&, + pro::proxy() &&> // + ::build {}; + +int main() { + pro::proxy p1 = pro::make_proxy( + [] { std::cout << "Lambda expression invoked\n"; }); + + // Implicit conversion via const reference of pro::proxy + pro::proxy p2 = p1; + std::cout << std::boolalpha << p2.has_value() << "\n"; // Prints "true" + + // Implicit conversion via rvalue reference of pro::proxy + pro::proxy p3 = std::move(p1); + std::cout << p1.has_value() << "\n"; // Prints "false" + (*p3)(); // Prints "Lambda expression invoked" + + // Different from implicit_conversion_dispatch, substitution from a null proxy + // is well-formed + pro::proxy p4 = p1; + std::cout << p4.has_value() << "\n"; // Prints "false" +} +``` + +## See Also + +- [`basic_facade_builder::add_facade`](../basic_facade_builder/add_facade.md) +- [alias template `skills::as_view`](../skills_as_view.md) +- [alias template `skills::as_weak`](../skills_as_weak.md) +- [class `implicit_conversion_dispatch`](../implicit_conversion_dispatch/README.md) diff --git a/docs/spec/substitution_dispatch/accessor.md b/docs/spec/substitution_dispatch/accessor.md new file mode 100644 index 00000000..e83fa001 --- /dev/null +++ b/docs/spec/substitution_dispatch/accessor.md @@ -0,0 +1,28 @@ +# Class template `substitution_dispatch::accessor` + +```cpp +// (1) +template +struct accessor { + accessor() = delete; +}; + +// (2) +template + requires(sizeof...(Os) > 1u && (std::is_constructible_v> && ...)) +struct accessor : accessor... { + using accessor::operator return-type-of...; +}; + +// (3) +template +struct accessor() cv ref noex> { + operator proxy() cv ref noex; +}; +``` + +`(1)` The default implementation of `accessor` is not constructible. + +`(2)` When `sizeof...(Os)` is greater than `1`, and `accessor...` are default-constructible, inherits all `accessor...` types and `using` their `operator return-type-of`. `return-type-of` denotes the *return type* of the overload type `O`. + +`(3)` When `sizeof...(Os)` is `1` and the only type `O` in `Os` is `proxy() cv ref noex`, provides an implicit `operator proxy()` with the same *cv ref noex* specifiers. `accessor::operator proxy()` is equivalent to `return proxy_invoke() cv ref noex>(static_cast

>(*this))` when `static_cast(*this).has_value()` is `true`, or `return nullptr` otherwise. diff --git a/docs/spec/substitution_dispatch/operator_call.md b/docs/spec/substitution_dispatch/operator_call.md new file mode 100644 index 00000000..4da0dff8 --- /dev/null +++ b/docs/spec/substitution_dispatch/operator_call.md @@ -0,0 +1,8 @@ +# `substitution_dispatch::operator()` + +```cpp +template +T&& operator()(T&& value) const noexcept; // exposition-only +``` + +Returns `std::forward(value)`. When `T` is not cv- or ref-qualified and [`is_bitwise_trivially_relocatable_v`](../is_bitwise_trivially_relocatable.md) is `true`, conversion from the return value to any `proxy` type shall perform bitwise trivial relocation and does not require that `T` is move-constructible. diff --git a/docs/spec/weak_proxy.md b/docs/spec/weak_proxy.md index bf84f5b1..64456e7e 100644 --- a/docs/spec/weak_proxy.md +++ b/docs/spec/weak_proxy.md @@ -13,13 +13,19 @@ template using weak_proxy = proxy>; ``` -Class template `weak_facade` is a [facade](facade.md) type for weak pointers (e.g., [`std::weak_ptr`](https://en.cppreference.com/w/cpp/memory/weak_ptr)) potentially converted from a `proxy` object. +`weak_proxy` is a non-owning handle that participates in the weak ownership model of an object that (when alive) models [`proxiable_target`](proxiable_target.md). It is analogous to `std::weak_ptr` relative to `std::shared_ptr`: it never extends the lifetime of the underlying object, but it can attempt to produce a (strong) `proxy` via `lock()`. + +`weak_facade` adapts the original [facade](facade.md) `F` so that: + +* A `lock()` member (direct convention) is provided, returning a `proxy` that contains a value if and only if the referenced object is still alive at the time of the call. +* All direct substitution conversions that would have produced a `proxy` become conversions that produce a `weak_proxy` instead (so that "weak-ness" is preserved across facade-substitution). +* No reflections from `F` are preserved. ## Member Types of `weak_facade` -| Name | Description | -| ------------------ | ------------------------------------------------------------ | -| `convention_types` | A [tuple-like](https://en.cppreference.com/w/cpp/utility/tuple/tuple-like) type that contains one convention type `C`, where`C::is_direct` is `false`, `C::dispatch_type` is of member function `lock` with accessibility, and `C::overload_types` is a [tuple-like](https://en.cppreference.com/w/cpp/utility/tuple/tuple-like) type that contains type `proxy() const noexcept`. | +| Name | Description | +| ---- | ----------- | +| `convention_types` | A [tuple-like](https://en.cppreference.com/w/cpp/utility/tuple/tuple-like) type transformed from `typename F::convention_types`. Specifically:
- It always prepends a direct convention whose dispatch type denotes the member function `lock` and whose single overload has signature `proxy() const noexcept`. Calling this overload attempts to obtain a strong `proxy`; it returns an empty `proxy` if the object has expired.
- For any direct convention `C` in `F` whose `dispatch_type` is `substitution_dispatch`, a transformed convention `C'` is included, whose
* `is_direct` is `true` and `dispatch_type` is still `substitution_dispatch`.
* For every overload `O` in `typename C::overload_types` with signature (after cv/ref/noexcept qualifiers) returning a `proxy`, replace its return type with `weak_proxy` while preserving qualifiers and `noexcept`.
- All other conventions from `F` are discarded. | | `reflection_types` | A [tuple-like](https://en.cppreference.com/w/cpp/utility/tuple/tuple-like) type that contains no types. | ## Member Constants of `weak_facade` diff --git a/include/proxy/v4/proxy.h b/include/proxy/v4/proxy.h index 0bb2cd35..fec34fd0 100644 --- a/include/proxy/v4/proxy.h +++ b/include/proxy/v4/proxy.h @@ -405,9 +405,9 @@ consteval bool diagnose_proxiable_required_convention_not_implemented() { template struct invocation_meta { - constexpr invocation_meta() = default; + invocation_meta() = default; template - constexpr explicit invocation_meta(std::in_place_type_t

) noexcept + constexpr explicit invocation_meta(std::in_place_type_t

) : dispatcher(overload_traits::template dispatcher) { } @@ -415,10 +415,10 @@ struct invocation_meta { }; template -struct composite_meta : Ms... { - constexpr composite_meta() noexcept = default; +struct PRO4D_ENFORCE_EBO composite_meta : Ms... { + composite_meta() = default; template - constexpr explicit composite_meta(std::in_place_type_t

) noexcept + constexpr explicit composite_meta(std::in_place_type_t

) : Ms(std::in_place_type

)... {} }; @@ -500,6 +500,7 @@ struct conv_traits template struct refl_meta { + refl_meta() = default; template requires(IsDirect) constexpr explicit refl_meta(std::in_place_type_t

) @@ -511,6 +512,7 @@ struct refl_meta { std::in_place_type::element_type>) { } + [[PROD_NO_UNIQUE_ADDRESS_ATTRIBUTE]] R reflector; }; @@ -829,9 +831,9 @@ using ptr_prototype = void* [2]; template struct meta_ptr_indirect_impl { - constexpr meta_ptr_indirect_impl() = default; + meta_ptr_indirect_impl() = default; template - constexpr explicit meta_ptr_indirect_impl(std::in_place_type_t

) noexcept + explicit meta_ptr_indirect_impl(std::in_place_type_t

) : ptr_(&storage

) {} bool has_value() const noexcept { return ptr_ != nullptr; } void reset() noexcept { ptr_ = nullptr; } @@ -2250,10 +2252,10 @@ struct proxy_cast_dispatch { #undef PROD_DEF_PROXY_CAST_ACCESSOR struct proxy_typeid_reflector { + proxy_typeid_reflector() = default; template constexpr explicit proxy_typeid_reflector(std::in_place_type_t) : info(&typeid(T)) {} - constexpr proxy_typeid_reflector(const proxy_typeid_reflector&) = default; template struct accessor { diff --git a/include/proxy/v4/proxy.ixx b/include/proxy/v4/proxy.ixx index 926b167c..f1ad5985 100644 --- a/include/proxy/v4/proxy.ixx +++ b/include/proxy/v4/proxy.ixx @@ -34,6 +34,7 @@ using v4::proxy_indirect_accessor; using v4::proxy_invoke; using v4::proxy_reflect; using v4::proxy_view; +using v4::substitution_dispatch; using v4::weak_dispatch; using v4::weak_facade; using v4::weak_proxy; diff --git a/include/proxy/v4/proxy_macros.h b/include/proxy/v4/proxy_macros.h index 5412644f..c21c80a4 100644 --- a/include/proxy/v4/proxy_macros.h +++ b/include/proxy/v4/proxy_macros.h @@ -28,7 +28,7 @@ #define PRO4D_DEBUG(...) __VA_ARGS__ #endif // NDEBUG -#define __msft_lib_proxy4 202508L +#define __msft_lib_proxy4 202510L #define PRO4D_DIRECT_FUNC_IMPL(...) \ noexcept(noexcept(__VA_ARGS__)) \ diff --git a/tests/proxy_lifetime_tests.cpp b/tests/proxy_lifetime_tests.cpp index 3bab65f8..0a338a02 100644 --- a/tests/proxy_lifetime_tests.cpp +++ b/tests/proxy_lifetime_tests.cpp @@ -30,7 +30,7 @@ struct TestRttiFacade : pro::facade_builder // ::add_facade // ::build {}; -// Additional static asserts for upward conversion +// Additional static asserts for substitution static_assert(!std::is_convertible_v, pro::proxy>); static_assert( @@ -1185,7 +1185,7 @@ TEST(ProxyLifetimeTests, Test_DirectConvension_Rvalue_Exception) { ASSERT_TRUE(tracker.GetOperations() == expected_ops); } -TEST(ProxyLifetimeTests, Test_UpwardCopyConvension_FromValue) { +TEST(ProxyLifetimeTests, Test_CopySubstitution_FromValue) { utils::LifetimeTracker tracker; std::vector expected_ops; { @@ -1209,14 +1209,14 @@ TEST(ProxyLifetimeTests, Test_UpwardCopyConvension_FromValue) { ASSERT_TRUE(tracker.GetOperations() == expected_ops); } -TEST(ProxyLifetimeTests, Test_UpwardCopyConvension_FromNull) { +TEST(ProxyLifetimeTests, Test_CopySubstitution_FromNull) { pro::proxy p1; pro::proxy p2 = p1; ASSERT_FALSE(p1.has_value()); ASSERT_FALSE(p2.has_value()); } -TEST(ProxyLifetimeTests, Test_UpwardMoveConvension_FromValue) { +TEST(ProxyLifetimeTests, Test_MoveSubstitution_FromValue) { utils::LifetimeTracker tracker; std::vector expected_ops; { @@ -1237,7 +1237,7 @@ TEST(ProxyLifetimeTests, Test_UpwardMoveConvension_FromValue) { ASSERT_TRUE(tracker.GetOperations() == expected_ops); } -TEST(ProxyLifetimeTests, Test_UpwardMoveConvension_FromNull) { +TEST(ProxyLifetimeTests, Test_MoveSubstitution_FromNull) { pro::proxy p1; pro::proxy p2 = std::move(p1); ASSERT_FALSE(p1.has_value()); diff --git a/tests/proxy_view_tests.cpp b/tests/proxy_view_tests.cpp index ca269e62..97dd1c92 100644 --- a/tests/proxy_view_tests.cpp +++ b/tests/proxy_view_tests.cpp @@ -136,13 +136,12 @@ TEST(ProxyViewTests, TestOverloadShadowing) { ASSERT_EQ((std::as_const(*p2))(), 1); } -TEST(ProxyViewTests, TestUpwardConversion_FromNull) { +TEST(ProxyViewTests, TestSubstitution_FromNull) { struct TestFacade1 : pro::facade_builder::build {}; - struct TestFacade2 - : pro::facade_builder // - ::add_facade // Supports upward conversion - ::add_skill // - ::build {}; + struct TestFacade2 : pro::facade_builder // + ::add_facade // Supports substitution + ::add_skill // + ::build {}; pro::proxy p1; pro::proxy_view p2 = p1; pro::proxy_view p3 = p2; @@ -151,17 +150,16 @@ TEST(ProxyViewTests, TestUpwardConversion_FromNull) { ASSERT_FALSE(p3.has_value()); } -TEST(ProxyViewTests, TestUpwardConversion_FromValue) { +TEST(ProxyViewTests, TestSubstitution_FromValue) { struct TestFacade1 : pro::facade_builder // ::add_convention // ::build {}; - struct TestFacade2 - : pro::facade_builder // - ::support_copy // - ::add_facade // Supports upward conversion - ::add_skill // - ::build {}; + struct TestFacade2 : pro::facade_builder // + ::support_copy // + ::add_facade // Supports substitution + ::add_skill // + ::build {}; pro::proxy p1 = pro::make_proxy(123); pro::proxy_view p2 = p1; pro::proxy_view p3 = p2;