From 6963dca505bb17f8a8a235aea79b73ac1d6afa32 Mon Sep 17 00:00:00 2001 From: Mingxin Wang Date: Mon, 9 Dec 2024 13:00:22 +0800 Subject: [PATCH] Revise design of `conversion_dispatch` (#212) --- .github/workflows/bvt-gcc.yml | 16 +- README.md | 4 +- docs/PRO_DEF_FREE_AS_MEM_DISPATCH.md | 2 +- docs/PRO_DEF_FREE_DISPATCH.md | 2 +- docs/PRO_DEF_MEM_DISPATCH.md | 2 +- docs/basic_facade_builder/add_convention.md | 3 +- docs/conversion_dispatch.md | 67 ------ docs/conversion_dispatch/accessor.md | 30 --- docs/conversion_dispatch/operator_call.md | 14 -- docs/explicit_conversion_dispatch.md | 44 ++++ docs/explicit_conversion_dispatch/accessor.md | 30 +++ .../operator_call.md | 8 + docs/implicit_conversion_dispatch.md | 57 +++++ docs/implicit_conversion_dispatch/accessor.md | 30 +++ .../operator_call.md | 8 + docs/operator_dispatch.md | 2 +- docs/operator_dispatch/accessor.md | 8 +- docs/specifications.md | 3 +- proxy.h | 208 ++++++++---------- .../basic_facade_builder/add_convention.cpp | 3 +- samples/conversion_dispatch.cpp | 41 ---- samples/explicit_conversion_dispatch.cpp | 16 ++ samples/implicit_conversion_dispatch.cpp | 31 +++ tests/proxy_dispatch_tests.cpp | 14 +- tests/proxy_lifetime_tests.cpp | 2 +- 25 files changed, 344 insertions(+), 301 deletions(-) delete mode 100644 docs/conversion_dispatch.md delete mode 100644 docs/conversion_dispatch/accessor.md delete mode 100644 docs/conversion_dispatch/operator_call.md create mode 100644 docs/explicit_conversion_dispatch.md create mode 100644 docs/explicit_conversion_dispatch/accessor.md create mode 100644 docs/explicit_conversion_dispatch/operator_call.md create mode 100644 docs/implicit_conversion_dispatch.md create mode 100644 docs/implicit_conversion_dispatch/accessor.md create mode 100644 docs/implicit_conversion_dispatch/operator_call.md delete mode 100644 samples/conversion_dispatch.cpp create mode 100644 samples/explicit_conversion_dispatch.cpp create mode 100644 samples/implicit_conversion_dispatch.cpp diff --git a/.github/workflows/bvt-gcc.yml b/.github/workflows/bvt-gcc.yml index fd7115f..ea33f23 100644 --- a/.github/workflows/bvt-gcc.yml +++ b/.github/workflows/bvt-gcc.yml @@ -15,27 +15,13 @@ jobs: - name: install gcc run: | - sudo apt install -y gcc-11 g++-11 gcc-12 g++-12 gcc-13 g++-13 gcc-14 g++-14 + sudo apt install -y gcc-13 g++-13 gcc-14 g++-14 - name: check compiler versions run: | - g++-11 --version - g++-12 --version g++-13 --version g++-14 --version - - name: build and run test with gcc 11 - run: | - cmake -B build-gcc-11 -DCMAKE_C_COMPILER=gcc-11 -DCMAKE_CXX_COMPILER=g++-11 -DCMAKE_BUILD_TYPE=Release - cmake --build build-gcc-11 -j - ctest --test-dir build-gcc-11 -j - - - name: build and run test with gcc 12 - run: | - cmake -B build-gcc-12 -DCMAKE_C_COMPILER=gcc-12 -DCMAKE_CXX_COMPILER=g++-12 -DCMAKE_BUILD_TYPE=Release - cmake --build build-gcc-12 -j - ctest --test-dir build-gcc-12 -j - - name: build and run test with gcc 13 run: | cmake -B build-gcc-13 -DCMAKE_C_COMPILER=gcc-13 -DCMAKE_CXX_COMPILER=g++-13 -DCMAKE_BUILD_TYPE=Release diff --git a/README.md b/README.md index 2011383..2b924d3 100644 --- a/README.md +++ b/README.md @@ -166,9 +166,9 @@ The "Proxy" library is a self-contained solution for runtime polymorphism in C++ | Family | Minimum version | Required flags | | ---------- | --------------- | -------------- | -| GCC | 11.2 | -std=c++20 | +| GCC | 13.1 | -std=c++20 | | Clang | 15.0.0 | -std=c++20 | -| MSVC | 19.30 | /std:c++20 | +| MSVC | 19.31 | /std:c++20 | | NVIDIA HPC | 24.1 | -std=c++20 | ## Build and Run Tests with CMake diff --git a/docs/PRO_DEF_FREE_AS_MEM_DISPATCH.md b/docs/PRO_DEF_FREE_AS_MEM_DISPATCH.md index a917e92..cb2e1d2 100644 --- a/docs/PRO_DEF_FREE_AS_MEM_DISPATCH.md +++ b/docs/PRO_DEF_FREE_AS_MEM_DISPATCH.md @@ -32,7 +32,7 @@ struct dispatch_name { accessor() = delete; }; template - requires(sizeof...(Os) > 1u && (std::is_trivial_v> && ...)) + requires(sizeof...(Os) > 1u && (std::is_constructible_v> && ...)) struct accessor : accessor... { using accessor::accessibility_func_name ...; }; diff --git a/docs/PRO_DEF_FREE_DISPATCH.md b/docs/PRO_DEF_FREE_DISPATCH.md index 710555e..07557ce 100644 --- a/docs/PRO_DEF_FREE_DISPATCH.md +++ b/docs/PRO_DEF_FREE_DISPATCH.md @@ -31,7 +31,7 @@ struct dispatch_name { accessor() = delete; }; template - requires(sizeof...(Os) > 1u && (std::is_trivial_v> && ...)) + requires(sizeof...(Os) > 1u && (std::is_constructible_v> && ...)) struct accessor : accessor... {}; template struct accessor { diff --git a/docs/PRO_DEF_MEM_DISPATCH.md b/docs/PRO_DEF_MEM_DISPATCH.md index 8239a84..8932d7b 100644 --- a/docs/PRO_DEF_MEM_DISPATCH.md +++ b/docs/PRO_DEF_MEM_DISPATCH.md @@ -32,7 +32,7 @@ struct dispatch_name { accessor() = delete; }; template - requires(sizeof...(Os) > 1u && (std::is_trivial_v> && ...)) + requires(sizeof...(Os) > 1u && (std::is_constructible_v> && ...)) struct accessor : accessor... { using accessor::accessibility_func_name ...; }; diff --git a/docs/basic_facade_builder/add_convention.md b/docs/basic_facade_builder/add_convention.md index afecaa6..bde23a0 100644 --- a/docs/basic_facade_builder/add_convention.md +++ b/docs/basic_facade_builder/add_convention.md @@ -49,8 +49,7 @@ struct BasicStringable : pro::facade_builder struct Stringable : pro::facade_builder ::add_facade ::support_copy - ::add_direct_convention>, - pro::proxy() &&> + ::add_direct_convention() &&> ::build {}; int main() { diff --git a/docs/conversion_dispatch.md b/docs/conversion_dispatch.md deleted file mode 100644 index fb795de..0000000 --- a/docs/conversion_dispatch.md +++ /dev/null @@ -1,67 +0,0 @@ -# Class template `conversion_dispatch` - -```cpp -template -class conversion_dispatch; -``` - -Class template `conversion_dispatch` is a [dispatch](ProDispatch.md) type for explicit or implicit type conversion expressions. It meets the [*ProAccessible* requirements](ProAccessible.md) of applicable types. `T` is the target type for conversion, and `Expl` specifies whether the conversion is explicit (defaults to `true`, as recommended in [C++ Core Guidelines C.164: Avoid implicit conversion operators](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c164-avoid-implicit-conversion-operators)). - -## Member Functions - -| Name | Description | -| ---------------------------------------------------- | ----------------------------------------- | -| (constructor) [trivial] | constructs a `conversion_dispatch` object | -| [`operator()`](conversion_dispatch/operator_call.md) | invokes the dispatch | - -## Member Types - -| Name | Description | -| --------------------------------------------- | --------------------------------- | -| [`accessor`](conversion_dispatch/accessor.md) | provides accessibility to `proxy` | - -## Example - -```cpp -#include -#include - -#include "proxy.h" - -struct DoubleConvertible : pro::facade_builder - ::add_convention, double() const> - ::build {}; - -struct Runnable : pro::facade_builder - ::add_convention, void()> - ::build {}; - -struct CopyableRunnable : pro::facade_builder - ::support_copy - ::add_facade - ::add_direct_convention, false>, - pro::proxy() const&, pro::proxy() &&> - ::build {}; - -int main() { - // Explicit conversion - pro::proxy p1 = pro::make_proxy(123); // p1 holds an integer - std::cout << std::fixed << std::setprecision(10) << std::boolalpha; - std::cout << static_cast(*p1) << "\n"; // Prints: "123.0000000000" - - // Implicit conversion - pro::proxy p2 = pro::make_proxy( - [] { std::cout << "Lambda expression invoked\n"; }); - auto p3 = p2; // Copy construction - pro::proxy p4 = p3; // Implicit conversion via const reference of pro::proxy - std::cout << p3.has_value() << "\n"; // Prints: "true" - // auto p5 = p4; // Won't compile because pro::proxy is not copy-constructible - pro::proxy p6 = std::move(p3); // Implicit conversion via rvalue reference of pro::proxy - std::cout << p3.has_value() << "\n"; // Prints: "false" - (*p6)(); // Prints: "Lambda expression invoked" -} -``` - -## See Also - -- [class template `operator_dispatch`](operator_dispatch.md) diff --git a/docs/conversion_dispatch/accessor.md b/docs/conversion_dispatch/accessor.md deleted file mode 100644 index de894b6..0000000 --- a/docs/conversion_dispatch/accessor.md +++ /dev/null @@ -1,30 +0,0 @@ -# Class template `conversion_dispatch::accessor` - -```cpp -// (1) -template -struct accessor { - accessor() = delete; -}; - -// (2) -template - requires(sizeof...(Os) > 1u && (std::is_trivial_v> && ...)) -struct accessor : accessor... { - using accessor::operator T...; -}; - -// (3) -template -struct accessor { - explicit(Expl) operator T() cv ref noex; -}; -``` - -Let `SELF` be `std::forward(*this)`. - -`(1)` The default implementation of `accessor` is not constructible. - -`(2)` When `sizeof...(Os)` is greater than `1`, and `accessor...` are trivial types, inherits all `accessor...` types and `using` their `operator T`. - -`(3)` When `sizeof...(Os)` is `1` and the only type `O` in `Os` is `T() cv ref noex`, provides an explicit (when `Expl` is `true`) or implicit (when `Expl` is `false`) `operator T()` with the same *cv ref noex* specifiers. `accessor::operator T()` is equivalent to `return proxy_invoke(access_proxy(SELF))`. diff --git a/docs/conversion_dispatch/operator_call.md b/docs/conversion_dispatch/operator_call.md deleted file mode 100644 index 7ced306..0000000 --- a/docs/conversion_dispatch/operator_call.md +++ /dev/null @@ -1,14 +0,0 @@ -# `conversion_dispatch::operator()` - -```cpp -template -T operator()(U&& value) - noexcept(std::conditional_t, - std::is_nothrow_convertible>::value) - requires(std::conditional_t, - std::is_convertible>::value); -``` - -Converts `value` to type `T`. When `Expl` is `true`, `std::is_constructible` is required to be `true`, or otherwise when `Expl` is `false`, `std::is_convertible` is required to be `true`. - -Returns `static_cast(std::forward(value))`. diff --git a/docs/explicit_conversion_dispatch.md b/docs/explicit_conversion_dispatch.md new file mode 100644 index 0000000..929ceb3 --- /dev/null +++ b/docs/explicit_conversion_dispatch.md @@ -0,0 +1,44 @@ +# Class `explicit_conversion_dispatch` + +```cpp +class explicit_conversion_dispatch; + +using conversion_dispatch = explicit_conversion_dispatch; +``` + +Class `explicit_conversion_dispatch` models a [dispatch](ProDispatch.md) type for explicit type conversion expressions. It meets the [*ProAccessible* requirements](ProAccessible.md) of applicable types. `conversion_dispatch` is an alias of `explicit_conversion_dispatch`. + +## Member Functions + +| Name | Description | +| ------------------------------------------------------------ | --------------------------------------------------- | +| (constructor) [nothrow] | constructs an `explicit_conversion_dispatch` object | +| [`operator()`](explicit_conversion_dispatch/operator_call.md) | invokes the dispatch | + +## Member Types + +| Name | Description | +| ------------------------------------------------------ | --------------------------------- | +| [`accessor`](explicit_conversion_dispatch/accessor.md) | provides accessibility to `proxy` | + +## Example + +```cpp +#include + +#include "proxy.h" + +struct IntConvertible : pro::facade_builder + ::add_convention + ::build {}; + +int main() { + pro::proxy p = pro::make_proxy(123); // p holds a short + std::cout << static_cast(*p) << "\n"; // Prints: "123" +} +``` + +## See Also + +- [class `implicit_conversion_dispatch`](implicit_conversion_dispatch.md) +- [class template `operator_dispatch`](operator_dispatch.md) diff --git a/docs/explicit_conversion_dispatch/accessor.md b/docs/explicit_conversion_dispatch/accessor.md new file mode 100644 index 0000000..a81237d --- /dev/null +++ b/docs/explicit_conversion_dispatch/accessor.md @@ -0,0 +1,30 @@ +# Class template `explicit_conversion_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 { + explicit operator T() cv ref noex; +}; +``` + +Let `SELF` be `std::forward(*this)`. + +`(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 `T() cv ref noex`, provides an explicit `operator T()` with the same *cv ref noex* specifiers. `accessor::operator T()` is equivalent to `return proxy_invoke(access_proxy(SELF))`. diff --git a/docs/explicit_conversion_dispatch/operator_call.md b/docs/explicit_conversion_dispatch/operator_call.md new file mode 100644 index 0000000..be8de4c --- /dev/null +++ b/docs/explicit_conversion_dispatch/operator_call.md @@ -0,0 +1,8 @@ +# `explicit_conversion_dispatch::operator()` + +```cpp +template +/* see below */ operator()(T&& value) noexcept; +``` + +Returns a value that is implicitly convertible to any type `U` with expression `U{std::forward(value)}` when `T` is explicitly convertible to type `U`. diff --git a/docs/implicit_conversion_dispatch.md b/docs/implicit_conversion_dispatch.md new file mode 100644 index 0000000..822ba0d --- /dev/null +++ b/docs/implicit_conversion_dispatch.md @@ -0,0 +1,57 @@ +# Class `implicit_conversion_dispatch` + +```cpp +class explicit_conversion_dispatch; +``` + +Class `implicit_conversion_dispatch` models a [dispatch](ProDispatch.md) type for implicit type conversion expressions. It meets the [*ProAccessible* requirements](ProAccessible.md) of applicable types. + +## Member Functions + +| Name | Description | +| ------------------------------------------------------------ | --------------------------------------------------- | +| (constructor) [nothrow] | constructs an `implicit_conversion_dispatch` object | +| [`operator()`](implicit_conversion_dispatch/operator_call.md) | invokes the dispatch | + +## Member Types + +| Name | Description | +| ------------------------------------------------------ | --------------------------------- | +| [`accessor`](implicit_conversion_dispatch/accessor.md) | provides accessibility to `proxy` | + +## Example + +```cpp +#include +#include + +#include "proxy.h" + +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"; }); + auto p2 = p1; // Copy construction + pro::proxy p3 = p2; // Implicit conversion via const reference of pro::proxy + std::cout << std::boolalpha << p2.has_value() << "\n"; // Prints: "true" + // auto p4 = p3; // Won't compile because pro::proxy is not copy-constructible + pro::proxy p5 = std::move(p2); // Implicit conversion via rvalue reference of pro::proxy + std::cout << p2.has_value() << "\n"; // Prints: "false" + (*p5)(); // Prints: "Lambda expression invoked" +} +``` + +## See Also + +- [class `explicit_conversion_dispatch`](explicit_conversion_dispatch.md) +- [class template `operator_dispatch`](operator_dispatch.md) \ No newline at end of file diff --git a/docs/implicit_conversion_dispatch/accessor.md b/docs/implicit_conversion_dispatch/accessor.md new file mode 100644 index 0000000..fa2ead8 --- /dev/null +++ b/docs/implicit_conversion_dispatch/accessor.md @@ -0,0 +1,30 @@ +# Class template `implicit_conversion_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 { + operator T() cv ref noex; +}; +``` + +Let `SELF` be `std::forward(*this)`. + +`(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 `T() cv ref noex`, provides an implicit `operator T()` with the same *cv ref noex* specifiers. `accessor::operator T()` is equivalent to `return proxy_invoke(access_proxy(SELF))`. diff --git a/docs/implicit_conversion_dispatch/operator_call.md b/docs/implicit_conversion_dispatch/operator_call.md new file mode 100644 index 0000000..7cd8ee3 --- /dev/null +++ b/docs/implicit_conversion_dispatch/operator_call.md @@ -0,0 +1,8 @@ +# `implicit_conversion_dispatch::operator()` + +```cpp +template +T&& operator()(T&& value) noexcept; +``` + +Returns `std::forward(value)`. diff --git a/docs/operator_dispatch.md b/docs/operator_dispatch.md index 6d5340e..758708b 100644 --- a/docs/operator_dispatch.md +++ b/docs/operator_dispatch.md @@ -104,7 +104,7 @@ Let `self` be the operand of [`proxy`](proxy.md), and `other` and `others...` be | Name | Description | | -------------------------------------------------- | ---------------------------------------- | -| (constructor) [trivial] | constructs an `operator_dispatch` object | +| (constructor) [nothrow] | constructs an `operator_dispatch` object | | [`operator()`](operator_dispatch/operator_call.md) | invokes the dispatch | ## Member Types diff --git a/docs/operator_dispatch/accessor.md b/docs/operator_dispatch/accessor.md index 6b9effc..513f568 100644 --- a/docs/operator_dispatch/accessor.md +++ b/docs/operator_dispatch/accessor.md @@ -17,13 +17,13 @@ For different `Sign` and `Rhs`, `operator_dispatch::accessor` has dif ```cpp // (2) template - requires(sizeof...(Os) > 1u && (std::is_trivial_v> && ...)) + requires(sizeof...(Os) > 1u && (std::is_constructible_v> && ...)) struct accessor : accessor... { using accessor::operator sop...; }; ``` -`(2)` When `sizeof...(Os)` is greater than `1`, and `accessor...` are trivial types, inherits all `accessor...` types and `using` their `operator sop`. +`(2)` When `sizeof...(Os)` is greater than `1`, and `accessor...` are default-constructible types, inherits all `accessor...` types and `using` their `operator sop`. When `Rhs` is `false`, the other specializations are defined as follows, where `sizeof...(Os)` is `1` and the only type `O` qualified with `cv ref noex` (let `SELF` be `std::forward(*this)`): @@ -74,11 +74,11 @@ struct accessor { ```cpp // (6) template - requires(sizeof...(Os) > 1u && (std::is_trivial_v> && ...)) + requires(sizeof...(Os) > 1u && (std::is_constructible_v> && ...)) struct accessor : accessor... {}; ``` -`(6)` When `sizeof...(Os)` is greater than `1`, and `accessor...` are trivial types, inherits all `accessor...` types. +`(6)` When `sizeof...(Os)` is greater than `1`, and `accessor...` are default-constructible types, inherits all `accessor...` types. When `Rhs` is `true`, the other specializations are defined as follows, where `sizeof...(Os)` is `1` and the only type `O` qualified with `cv ref noex` (let `SELF` be `std::forward(self)`): diff --git a/docs/specifications.md b/docs/specifications.md index 8224fa9..f997d88 100644 --- a/docs/specifications.md +++ b/docs/specifications.md @@ -19,7 +19,8 @@ This document provides the API specifications for the C++ library Proxy (version | [`constraint_level`](constraint_level.md) | Defines the 4 constraint levels of a special member function | | [`proxiable_ptr_constraints`](proxiable_ptr_constraints.md) | Defines the constraints of a pointer type to instantiate a `proxy` | | [`operator_dispatch`](operator_dispatch.md) | Dispatch type for operator expressions with accessibility | -| [`conversion_dispatch`](conversion_dispatch.md) | Dispatch type for conversion expressions with accessibility | +| [`explicit_conversion_dispatch`
`conversion_dispatch`](explicit_conversion_dispatch.md) | Dispatch type for explicit conversion expressions with accessibility | +| [`implicit_conversion_dispatch`](implicit_conversion_dispatch.md) | Dispatch type for implicit conversion expressions with accessibility | ## Functions diff --git a/proxy.h b/proxy.h index 67606ec..d9aadca 100644 --- a/proxy.h +++ b/proxy.h @@ -29,6 +29,12 @@ #define ___PRO_ENFORCE_EBO #endif // _MSC_VER +#ifdef NDEBUG +#define ___PRO_DEBUG(...) +#else +#define ___PRO_DEBUG(...) __VA_ARGS__ +#endif // NDEBUG + #define __msft_lib_proxy 202410L namespace pro { @@ -260,6 +266,7 @@ struct overload_traits_impl : applicable_traits { } } }; + using return_type = R; template static constexpr bool applicable_ptr = @@ -687,24 +694,24 @@ class proxy : public details::facade_traits::direct_accessor { using _IA = details::proxy_indirect_accessor; public: -#ifdef NDEBUG - proxy() noexcept = default; -#else proxy() noexcept { - if constexpr (_Traits::has_indirection) { - std::ignore = static_cast<_IA* (proxy::*)() noexcept>(&proxy::operator->); - std::ignore = static_cast( - &proxy::operator->); - std::ignore = static_cast<_IA& (proxy::*)() & noexcept>(&proxy::operator*); - std::ignore = static_cast( - &proxy::operator*); - std::ignore = static_cast<_IA&& (proxy::*)() && noexcept>( - &proxy::operator*); - std::ignore = static_cast( - &proxy::operator*); - } + ___PRO_DEBUG( + if constexpr (_Traits::has_indirection) { + std::ignore = static_cast<_IA* (proxy::*)() noexcept>( + &proxy::operator->); + std::ignore = static_cast( + &proxy::operator->); + std::ignore = static_cast<_IA& (proxy::*)() & noexcept>( + &proxy::operator*); + std::ignore = static_cast( + &proxy::operator*); + std::ignore = static_cast<_IA&& (proxy::*)() && noexcept>( + &proxy::operator*); + std::ignore = static_cast( + &proxy::operator*); + } + ) } -#endif // NDEBUG proxy(std::nullptr_t) noexcept : proxy() {} proxy(const proxy&) noexcept requires(F::constraints.copyability == constraint_level::trivial) = default; @@ -1176,15 +1183,52 @@ proxy make_proxy(T&& value) { __MACRO(const&& noexcept, noexcept, const accessor&& __self, \ ::std::forward(__self), __VA_ARGS__); -#ifdef NDEBUG -#define ___PRO_GEN_SYMBOL_FOR_MEM_ACCESSOR(...) -#else -#define ___PRO_GEN_SYMBOL_FOR_MEM_ACCESSOR(...) \ - accessor() noexcept { ::std::ignore = &accessor::__VA_ARGS__; } -#endif // NDEBUG +#define ___PRO_GEN_DEBUG_SYMBOL_FOR_MEM_ACCESSOR(...) \ + ___PRO_DEBUG( \ + accessor() noexcept { ::std::ignore = &accessor::__VA_ARGS__; }) namespace details { +template +using overload_return_type = typename overload_traits::return_type; +#define ___PRO_DEF_CAST_ACCESSOR(Q, SELF, ...) \ + template \ + struct accessor { \ + ___PRO_GEN_DEBUG_SYMBOL_FOR_MEM_ACCESSOR(operator T) \ + explicit(Expl) operator T() Q { \ + if constexpr (Nullable) { \ + if (!access_proxy(SELF).has_value()) { return nullptr; } \ + } \ + return proxy_invoke(access_proxy(SELF)); \ + } \ + } +template +struct cast_dispatch_base { + ___PRO_DEF_MEM_ACCESSOR_TEMPLATE(___PRO_DEF_CAST_ACCESSOR, + operator overload_return_type<__Os>) +}; +#undef ___PRO_DEF_CAST_ACCESSOR + +struct upward_conversion_dispatch : cast_dispatch_base { + template + T&& operator()(T&& self) noexcept { return std::forward(self); } +}; + +template +struct explicit_conversion_adapter { + explicit explicit_conversion_adapter(T&& value) noexcept + : value_(std::forward(value)) {} + explicit_conversion_adapter(const explicit_conversion_adapter&) = delete; + + template + operator U() noexcept(std::is_nothrow_constructible_v) + requires(std::is_constructible_v) + { return U{std::forward(value_)}; } + + private: + T&& value_; +}; + constexpr std::size_t invalid_size = std::numeric_limits::max(); constexpr constraint_level invalid_cl = static_cast( std::numeric_limits>::min()); @@ -1257,29 +1301,6 @@ struct facade_impl { static constexpr proxiable_ptr_constraints constraints = C; }; -#define ___PRO_DEF_UPWARD_CONVERSION_ACCESSOR(Q, SELF, ...) \ - template \ - struct accessor() Q> { \ - ___PRO_GEN_SYMBOL_FOR_MEM_ACCESSOR(__VA_ARGS__) \ - __VA_ARGS__ () Q { \ - if (access_proxy(SELF).has_value()) { \ - return proxy_invoke() Q>(access_proxy(SELF)); \ - } \ - return nullptr; \ - } \ - } -template -struct upward_conversion_dispatch { - template - proxy operator()(T&& value) - noexcept(std::is_nothrow_convertible_v>) - requires(std::is_convertible_v>) - { return static_cast>(std::forward(value)); } - ___PRO_DEF_MEM_ACCESSOR_TEMPLATE( - ___PRO_DEF_UPWARD_CONVERSION_ACCESSOR, operator proxy) -}; -#undef ___PRO_DEF_UPWARD_CONVERSION_ACCESSOR - template struct add_tuple_reduction : std::type_identity {}; template requires(!std::is_same_v && ...) @@ -1325,16 +1346,16 @@ using move_conversion_overload = template struct add_upward_conversion_conv : std::type_identity, copy_conversion_overload, + upward_conversion_dispatch, copy_conversion_overload, move_conversion_overload>>> {}; template struct add_upward_conversion_conv : std::type_identity, move_conversion_overload>>> {}; + upward_conversion_dispatch, move_conversion_overload>>> {}; template struct add_upward_conversion_conv : std::type_identity, copy_conversion_overload>>> {}; + upward_conversion_dispatch, copy_conversion_overload>>> {}; template struct add_upward_conversion_conv< Cs, F, constraint_level::none, constraint_level::none> @@ -1423,15 +1444,15 @@ struct operator_dispatch; #define ___PRO_DEF_LHS_LEFT_OP_ACCESSOR(Q, SELF, ...) \ template \ struct accessor { \ - ___PRO_GEN_SYMBOL_FOR_MEM_ACCESSOR(__VA_ARGS__) \ - R __VA_ARGS__ () Q \ + ___PRO_GEN_DEBUG_SYMBOL_FOR_MEM_ACCESSOR(__VA_ARGS__) \ + R __VA_ARGS__() Q \ { return proxy_invoke(access_proxy(SELF)); } \ } #define ___PRO_DEF_LHS_ANY_OP_ACCESSOR(Q, SELF, ...) \ template \ struct accessor { \ - ___PRO_GEN_SYMBOL_FOR_MEM_ACCESSOR(__VA_ARGS__) \ - R __VA_ARGS__ (Args... args) Q { \ + ___PRO_GEN_DEBUG_SYMBOL_FOR_MEM_ACCESSOR(__VA_ARGS__) \ + R __VA_ARGS__(Args... args) Q { \ return proxy_invoke( \ access_proxy(SELF), std::forward(args)...); \ } \ @@ -1466,30 +1487,21 @@ struct operator_dispatch; operator __VA_ARGS__) \ }; -#ifdef NDEBUG #define ___PRO_DEF_RHS_OP_ACCESSOR(Q, NE, SELF, FW_SELF, ...) \ template \ struct accessor { \ - friend R operator __VA_ARGS__ (Arg arg, SELF) NE { \ + friend R operator __VA_ARGS__(Arg arg, SELF) NE { \ return proxy_invoke( \ access_proxy(FW_SELF), std::forward(arg)); \ } \ - } -#else -#define ___PRO_DEF_RHS_OP_ACCESSOR(Q, NE, SELF, FW_SELF, ...) \ - template \ - struct accessor { \ +___PRO_DEBUG( \ accessor() noexcept { std::ignore = &accessor::_symbol_guard; } \ - friend R operator __VA_ARGS__ (Arg arg, SELF) NE { \ - return proxy_invoke( \ - access_proxy(FW_SELF), std::forward(arg)); \ - } \ \ private: \ static inline R _symbol_guard(Arg arg, SELF) NE \ { return std::forward(arg) __VA_ARGS__ FW_SELF; } \ +) \ } -#endif // NDEBUG #define ___PRO_RHS_OP_DISPATCH_IMPL(...) \ template <> \ struct operator_dispatch<#__VA_ARGS__, true> { \ @@ -1512,8 +1524,8 @@ struct operator_dispatch; #define ___PRO_DEF_LHS_ASSIGNMENT_OP_ACCESSOR(Q, SELF, ...) \ template \ struct accessor { \ - ___PRO_GEN_SYMBOL_FOR_MEM_ACCESSOR(__VA_ARGS__) \ - decltype(auto) __VA_ARGS__ (Arg arg) Q { \ + ___PRO_GEN_DEBUG_SYMBOL_FOR_MEM_ACCESSOR(__VA_ARGS__) \ + decltype(auto) __VA_ARGS__(Arg arg) Q { \ proxy_invoke( \ access_proxy(SELF), std::forward(arg)); \ if constexpr (C::is_direct) { \ @@ -1523,30 +1535,21 @@ struct operator_dispatch; } \ } \ } -#ifdef NDEBUG #define ___PRO_DEF_RHS_ASSIGNMENT_OP_ACCESSOR(Q, NE, SELF, FW_SELF, ...) \ template \ struct accessor { \ - friend Arg& operator __VA_ARGS__ (Arg& arg, SELF) NE { \ + friend Arg& operator __VA_ARGS__(Arg& arg, SELF) NE { \ proxy_invoke(access_proxy(FW_SELF), arg); \ return arg; \ } \ - } -#else -#define ___PRO_DEF_RHS_ASSIGNMENT_OP_ACCESSOR(Q, NE, SELF, FW_SELF, ...) \ - template \ - struct accessor { \ +___PRO_DEBUG( \ accessor() noexcept { std::ignore = &accessor::_symbol_guard; } \ - friend Arg& operator __VA_ARGS__ (Arg& arg, SELF) NE { \ - proxy_invoke(access_proxy(FW_SELF), arg); \ - return arg; \ - } \ \ private: \ static inline Arg& _symbol_guard(Arg& arg, SELF) NE \ { return arg __VA_ARGS__ FW_SELF; } \ +) \ } -#endif // NDEBUG #define ___PRO_ASSIGNMENT_OP_DISPATCH_IMPL(...) \ template <> \ struct operator_dispatch<#__VA_ARGS__, false> { \ @@ -1643,25 +1646,18 @@ struct operator_dispatch<"[]", false> { #undef ___PRO_DEF_LHS_ANY_OP_ACCESSOR #undef ___PRO_DEF_LHS_LEFT_OP_ACCESSOR -#define ___PRO_DEF_CONVERSION_ACCESSOR(Q, SELF, ...) \ - template \ - struct accessor { \ - ___PRO_GEN_SYMBOL_FOR_MEM_ACCESSOR(__VA_ARGS__) \ - explicit(Expl) __VA_ARGS__ () Q \ - { return proxy_invoke(access_proxy(SELF)); } \ - } -template -struct conversion_dispatch { - template - T operator()(U&& value) - noexcept(std::conditional_t, - std::is_nothrow_convertible>::value) - requires(std::conditional_t, - std::is_convertible>::value) - { return static_cast(std::forward(value)); } - ___PRO_DEF_MEM_ACCESSOR_TEMPLATE(___PRO_DEF_CONVERSION_ACCESSOR, operator T) +struct implicit_conversion_dispatch + : details::cast_dispatch_base { + template + T&& operator()(T&& self) noexcept { return std::forward(self); } +}; +struct explicit_conversion_dispatch + : details::cast_dispatch_base { + template + auto operator()(T&& self) noexcept + { return details::explicit_conversion_adapter{std::forward(self)}; } }; -#undef ___PRO_DEF_CONVERSION_ACCESSOR +using conversion_dispatch = explicit_conversion_dispatch; #define ___PRO_EXPAND_IMPL(__X) __X #define ___PRO_EXPAND_MACRO_IMPL( \ @@ -1674,8 +1670,8 @@ struct conversion_dispatch { #define ___PRO_DEF_MEM_ACCESSOR(__Q, __SELF, ...) \ template \ struct accessor<__F, __C, __R(__Args...) __Q> { \ - ___PRO_GEN_SYMBOL_FOR_MEM_ACCESSOR(__VA_ARGS__) \ - __R __VA_ARGS__ (__Args... __args) __Q { \ + ___PRO_GEN_DEBUG_SYMBOL_FOR_MEM_ACCESSOR(__VA_ARGS__) \ + __R __VA_ARGS__(__Args... __args) __Q { \ return ::pro::proxy_invoke<__C, __R(__Args...) __Q>( \ ::pro::access_proxy<__F>(__SELF), \ ::std::forward<__Args>(__args)...); \ @@ -1696,33 +1692,23 @@ struct conversion_dispatch { #define PRO_DEF_MEM_DISPATCH(__NAME, ...) \ ___PRO_EXPAND_MACRO(___PRO_DEF_MEM_DISPATCH, __NAME, __VA_ARGS__) -#ifdef NDEBUG -#define ___PRO_DEF_FREE_ACCESSOR(__Q, __NE, __SELF, __FW_SELF, ...) \ - template \ - struct accessor<__F, __C, __R(__Args...) __Q> { \ - friend __R __VA_ARGS__ (__SELF, __Args... __args) __NE { \ - return ::pro::proxy_invoke<__C, __R(__Args...) __Q>( \ - ::pro::access_proxy<__F>(__FW_SELF), \ - ::std::forward<__Args>(__args)...); \ - } \ - } -#else #define ___PRO_DEF_FREE_ACCESSOR(__Q, __NE, __SELF, __FW_SELF, ...) \ template \ struct accessor<__F, __C, __R(__Args...) __Q> { \ - accessor() noexcept { ::std::ignore = &accessor::_symbol_guard; } \ friend __R __VA_ARGS__(__SELF, __Args... __args) __NE { \ return ::pro::proxy_invoke<__C, __R(__Args...) __Q>( \ ::pro::access_proxy<__F>(__FW_SELF), \ ::std::forward<__Args>(__args)...); \ } \ +___PRO_DEBUG( \ + accessor() noexcept { ::std::ignore = &accessor::_symbol_guard; } \ \ private: \ static inline __R _symbol_guard(__SELF, __Args... __args) __NE { \ return __VA_ARGS__(__FW_SELF, ::std::forward<__Args>(__args)...); \ } \ +) \ } -#endif // NDEBUG #define ___PRO_DEF_FREE_DISPATCH_IMPL(__NAME, __FUNC, __FNAME) \ struct __NAME { \ template \ diff --git a/samples/basic_facade_builder/add_convention.cpp b/samples/basic_facade_builder/add_convention.cpp index 5d44cc8..df65294 100644 --- a/samples/basic_facade_builder/add_convention.cpp +++ b/samples/basic_facade_builder/add_convention.cpp @@ -17,8 +17,7 @@ struct BasicStringable : pro::facade_builder struct Stringable : pro::facade_builder ::add_facade ::support_copy - ::add_direct_convention>, - pro::proxy() &&> + ::add_direct_convention() &&> ::build {}; int main() { diff --git a/samples/conversion_dispatch.cpp b/samples/conversion_dispatch.cpp deleted file mode 100644 index 4140e51..0000000 --- a/samples/conversion_dispatch.cpp +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -// This file contains example code from conversion_dispatch.md. - -#include -#include - -#include "proxy.h" - -struct DoubleConvertible : pro::facade_builder - ::add_convention, double() const> - ::build {}; - -struct Runnable : pro::facade_builder - ::add_convention, void()> - ::build {}; - -struct CopyableRunnable : pro::facade_builder - ::support_copy - ::add_facade - ::add_direct_convention, false>, - pro::proxy() const&, pro::proxy() &&> - ::build {}; - -int main() { - // Explicit conversion - pro::proxy p1 = pro::make_proxy(123); // p1 holds an integer - std::cout << std::fixed << std::setprecision(10) << std::boolalpha; - std::cout << static_cast(*p1) << "\n"; // Prints: "123.0000000000" - - // Implicit conversion - pro::proxy p2 = pro::make_proxy( - [] { std::cout << "Lambda expression invoked\n"; }); - auto p3 = p2; // Copy construction - pro::proxy p4 = p3; // Implicit conversion via const reference of pro::proxy - std::cout << p3.has_value() << "\n"; // Prints: "true" - // auto p5 = p4; // Won't compile because pro::proxy is not copy-constructible - pro::proxy p6 = std::move(p3); // Implicit conversion via rvalue reference of pro::proxy - std::cout << p3.has_value() << "\n"; // Prints: "false" - (*p6)(); // Prints: "Lambda expression invoked" -} diff --git a/samples/explicit_conversion_dispatch.cpp b/samples/explicit_conversion_dispatch.cpp new file mode 100644 index 0000000..05349c5 --- /dev/null +++ b/samples/explicit_conversion_dispatch.cpp @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +// This file contains example code from explicit_conversion_dispatch.md. + +#include + +#include "proxy.h" + +struct IntConvertible : pro::facade_builder + ::add_convention + ::build {}; + +int main() { + pro::proxy p = pro::make_proxy(123); // p holds a short + std::cout << static_cast(*p) << "\n"; // Prints: "123" +} diff --git a/samples/implicit_conversion_dispatch.cpp b/samples/implicit_conversion_dispatch.cpp new file mode 100644 index 0000000..b8d49c2 --- /dev/null +++ b/samples/implicit_conversion_dispatch.cpp @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +// This file contains example code from implicit_conversion_dispatch.md. + +#include +#include + +#include "proxy.h" + +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"; }); + auto p2 = p1; // Copy construction + pro::proxy p3 = p2; // Implicit conversion via const reference of pro::proxy + std::cout << std::boolalpha << p2.has_value() << "\n"; // Prints: "true" + // auto p4 = p3; // Won't compile because pro::proxy is not copy-constructible + pro::proxy p5 = std::move(p2); // Implicit conversion via rvalue reference of pro::proxy + std::cout << p2.has_value() << "\n"; // Prints: "false" + (*p5)(); // Prints: "Lambda expression invoked" +} diff --git a/tests/proxy_dispatch_tests.cpp b/tests/proxy_dispatch_tests.cpp index 77dc798..090d2a6 100644 --- a/tests/proxy_dispatch_tests.cpp +++ b/tests/proxy_dispatch_tests.cpp @@ -587,10 +587,10 @@ TEST(ProxyDispatchTests, TestRhsOpPtrToMem) { } TEST(ProxyDispatchTests, TestIndirectConversion) { - struct TestFacade : pro::facade_builder::add_convention, int()>::build {}; - double v = 12.3; + struct TestFacade : pro::facade_builder::add_convention::build {}; + short v = 123; pro::proxy p = &v; - ASSERT_EQ(static_cast(*p), 12); + ASSERT_EQ(static_cast(*p), 123); } TEST(ProxyDispatchTests, TestDirectConversion) { @@ -600,7 +600,7 @@ TEST(ProxyDispatchTests, TestDirectConversion) { struct TestFacade : pro::facade_builder ::add_facade ::add_convention, void(int val)> - ::add_direct_convention>, pro::proxy() &&> + ::add_direct_convention() &&> ::build {}; pro::proxy p1 = std::make_unique(123); *p1 += 3; @@ -612,11 +612,11 @@ TEST(ProxyDispatchTests, TestDirectConversion) { } TEST(ProxyDispatchTests, TestImplciitConversion) { - struct TestFacade : pro::facade_builder::add_convention, int()>::build {}; - double v = 12.3; + struct TestFacade : pro::facade_builder::add_convention::build {}; + short v = 123; pro::proxy p = &v; int converted = *p; - ASSERT_EQ(converted, 12); + ASSERT_EQ(converted, 123); } TEST(ProxyDispatchTests, TestFreeAsMemDispatch) { diff --git a/tests/proxy_lifetime_tests.cpp b/tests/proxy_lifetime_tests.cpp index c5b480e..81a8d69 100644 --- a/tests/proxy_lifetime_tests.cpp +++ b/tests/proxy_lifetime_tests.cpp @@ -11,7 +11,7 @@ struct TestFacade : pro::facade_builder ::add_convention ::support_relocation ::support_copy - ::add_direct_convention, utils::LifetimeTracker::Session() const&, utils::LifetimeTracker::Session()&&> + ::add_direct_convention ::build {}; struct TestTrivialFacade : pro::facade_builder