diff --git a/proxy.h b/proxy.h index 3dc3c25..437dfe9 100644 --- a/proxy.h +++ b/proxy.h @@ -15,6 +15,14 @@ #include #include +#ifdef __cpp_rtti +#ifndef __cpp_exceptions +#include // For std::abort() when "throw" is not available +#endif // __cpp_exceptions +#include +#include +#endif // __cpp_rtti + #if __has_cpp_attribute(msvc::no_unique_address) #define ___PRO_NO_UNIQUE_ADDRESS_ATTRIBUTE msvc::no_unique_address #elif __has_cpp_attribute(no_unique_address) @@ -1240,6 +1248,10 @@ using proxy_view = proxy>; ___PRO_DEBUG( \ accessor() noexcept { ::std::ignore = &accessor::__VA_ARGS__; }) +#ifdef __cpp_rtti +struct bad_proxy_cast : std::bad_cast {}; +#endif // __cpp_rtti + namespace details { template @@ -1537,6 +1549,113 @@ struct sign { template sign(const char (&str)[N]) -> sign; +#ifdef __cpp_rtti +#ifdef __cpp_exceptions +#define ___PRO_THROW(...) throw __VA_ARGS__ +#else +#define ___PRO_THROW(...) std::abort() +#endif // __cpp_exceptions + +struct proxy_cast_context { + const std::type_info* type_ptr; + bool is_ref; + bool is_const; + void* result_ptr; +}; + +template +struct proxy_cast_accessor_impl { + using _Self = + add_qualifier_t, overload_traits::qualifier>; + template + friend T proxy_cast(_Self self) { + static_assert(!std::is_rvalue_reference_v); + if (!access_proxy(self).has_value()) { ___PRO_THROW(bad_proxy_cast{}); } + if constexpr (std::is_lvalue_reference_v) { + using U = std::remove_reference_t; + void* result = nullptr; + proxy_cast_context ctx{.type_ptr = &typeid(T), .is_ref = true, + .is_const = std::is_const_v, .result_ptr = &result}; + proxy_invoke(access_proxy(std::forward<_Self>(self)), ctx); + if (result == nullptr) { ___PRO_THROW(bad_proxy_cast{}); } + return *static_cast(result); + } else { + std::optional> result; + proxy_cast_context ctx{.type_ptr = &typeid(T), .is_ref = false, + .is_const = false, .result_ptr = &result}; + proxy_invoke(access_proxy(std::forward<_Self>(self)), ctx); + if (!result.has_value()) { ___PRO_THROW(bad_proxy_cast{}); } + return std::move(*result); + } + } + template + friend T* proxy_cast(std::remove_reference_t<_Self>* self) noexcept + requires(std::is_lvalue_reference_v<_Self>) { + if (!access_proxy(*self).has_value()) { return nullptr; } + void* result = nullptr; + proxy_cast_context ctx{.type_ptr = &typeid(T), .is_ref = true, + .is_const = std::is_const_v, .result_ptr = &result}; + proxy_invoke(access_proxy(*self), ctx); + return static_cast(result); + } +}; + +#define ___PRO_DEF_PROXY_CAST_ACCESSOR(Q, ...) \ + template \ + struct accessor \ + : proxy_cast_accessor_impl {} +struct proxy_cast_dispatch { + template + void operator()(T&& self, proxy_cast_context ctx) { + if (typeid(T) == *ctx.type_ptr) { + if (ctx.is_ref) { + if constexpr (std::is_lvalue_reference_v) { + if (ctx.is_const || !std::is_const_v) { + *static_cast(ctx.result_ptr) = (void*)&self; + } + } + } else { + if constexpr (std::is_constructible_v, T>) { + static_cast>*>(ctx.result_ptr) + ->emplace(std::forward(self)); + } + } + } + } + ___PRO_DEF_FREE_ACCESSOR_TEMPLATE(___PRO_DEF_PROXY_CAST_ACCESSOR) +}; +#undef ___PRO_DEF_PROXY_CAST_ACCESSOR + +struct proxy_typeid_reflector { + 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 { + friend const std::type_info& proxy_typeid( + const adl_accessor_arg_t& self) noexcept { + const proxy& p = access_proxy(self); + if (!p.has_value()) { return typeid(void); } + const proxy_typeid_reflector& refl = proxy_reflect(p); + return *refl.info; + } +___PRO_DEBUG( + accessor() noexcept { std::ignore = &accessor::_symbol_guard; } + + private: + static inline const std::type_info& _symbol_guard( + const adl_accessor_arg_t& self) noexcept + { return proxy_typeid(self); } +) + }; + + const std::type_info* info; +}; +#undef ___PRO_THROW +#endif // __cpp_rtti + } // namespace details template @@ -1582,6 +1701,25 @@ struct basic_facade_builder { template using support_destruction = basic_facade_builder< Cs, Rs, details::make_destructible(C, CL)>; +#ifdef __cpp_rtti + using support_indirect_rtti = + basic_facade_builder< + details::add_conv_t>, + details::add_tuple_t>, C>; + using support_direct_rtti = + basic_facade_builder< + details::add_conv_t>, + details::add_tuple_t>, C>; + using support_rtti = support_indirect_rtti; +#endif // __cpp_rtti template using add_view = add_direct_convention< details::proxy_view_dispatch, details::proxy_view_overload>; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index cb2870a..49a6987 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -24,6 +24,7 @@ add_executable(msft_proxy_tests proxy_lifetime_tests.cpp proxy_reflection_tests.cpp proxy_regression_tests.cpp + proxy_rtti_tests.cpp proxy_traits_tests.cpp proxy_view_tests.cpp ) diff --git a/tests/proxy_rtti_tests.cpp b/tests/proxy_rtti_tests.cpp new file mode 100644 index 0000000..6a78b0e --- /dev/null +++ b/tests/proxy_rtti_tests.cpp @@ -0,0 +1,293 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include +#include +#include "proxy.h" + +namespace proxy_rtti_tests_details { + +struct TestFacade : pro::facade_builder + ::support_rtti + ::support_direct_rtti + ::build {}; + +} // namespace proxy_rtti_tests_details + +namespace details = proxy_rtti_tests_details; + +TEST(ProxyRttiTests, TestIndirectCast_Void_Fail) { + pro::proxy p; + bool exception_thrown = false; + try { + proxy_cast(*p); + } catch (const pro::bad_proxy_cast&) { + exception_thrown = true; + } + ASSERT_TRUE(exception_thrown); +} + +TEST(ProxyRttiTests, TestIndirectCast_Ref_Succeed) { + int v = 123; + pro::proxy p = &v; + proxy_cast(*p) = 456; + ASSERT_EQ(v, 456); +} + +TEST(ProxyRttiTests, TestIndirectCast_Ref_Fail) { + int v = 123; + pro::proxy p = &v; + bool exception_thrown = false; + try { + proxy_cast(*p); + } catch (const pro::bad_proxy_cast&) { + exception_thrown = true; + } + ASSERT_TRUE(exception_thrown); +} + +TEST(ProxyRttiTests, TestIndirectCast_ConstRef_Succeed) { + int v = 123; + pro::proxy p = &v; + const int& r = proxy_cast(*p); + ASSERT_EQ(&v, &r); + ASSERT_EQ(v, 123); +} + +TEST(ProxyRttiTests, TestIndirectCast_ConstRef_Fail) { + int v = 123; + pro::proxy p = &v; + bool exception_thrown = false; + try { + proxy_cast(*p); + } catch (const pro::bad_proxy_cast&) { + exception_thrown = true; + } + ASSERT_TRUE(exception_thrown); +} + +TEST(ProxyRttiTests, TestIndirectCast_Copy_Succeed) { + int v1 = 123; + pro::proxy p = &v1; + int v2 = proxy_cast(*p); + ASSERT_EQ(v1, 123); + ASSERT_EQ(v2, 123); +} + +TEST(ProxyRttiTests, TestIndirectCast_Copy_Fail) { + int v = 123; + pro::proxy p = &v; + bool exception_thrown = false; + try { + proxy_cast(*p); + } catch (const pro::bad_proxy_cast&) { + exception_thrown = true; + } + ASSERT_TRUE(exception_thrown); +} + +TEST(ProxyRttiTests, TestIndirectCast_Move_Succeed) { + const std::vector v1{1, 2, 3}; + auto p = pro::make_proxy(v1); + auto v2 = proxy_cast>(std::move(*p)); + ASSERT_EQ(v2, v1); + v2 = proxy_cast>(std::move(*p)); + ASSERT_TRUE(v2.empty()); +} + +TEST(ProxyRttiTests, TestIndirectCast_Move_Fail) { + auto p = pro::make_proxy(std::vector{1, 2, 3}); + bool exception_thrown = false; + try { + proxy_cast>(std::move(*p)); + } catch (const pro::bad_proxy_cast&) { + exception_thrown = true; + } + ASSERT_TRUE(exception_thrown); +} + +TEST(ProxyRttiTests, TestIndirectCast_Ptr_Succeed) { + int v = 123; + pro::proxy p = &v; + auto ptr = proxy_cast(&*p); + static_assert(std::is_same_v); + ASSERT_EQ(ptr, &v); + ASSERT_EQ(v, 123); +} + +TEST(ProxyRttiTests, TestIndirectCast_Ptr_Fail) { + int v = 123; + pro::proxy p = &v; + auto ptr = proxy_cast(&*p); + static_assert(std::is_same_v); + ASSERT_EQ(ptr, nullptr); + ASSERT_EQ(v, 123); +} + +TEST(ProxyRttiTests, TestIndirectCast_ConstPtr_Succeed) { + int v = 123; + pro::proxy p = &v; + auto ptr = proxy_cast(&*p); + static_assert(std::is_same_v); + ASSERT_EQ(ptr, &v); + ASSERT_EQ(v, 123); +} + +TEST(ProxyRttiTests, TestIndirectCast_ConstPtr_Fail) { + int v = 123; + pro::proxy p = &v; + auto ptr = proxy_cast(&*p); + static_assert(std::is_same_v); + ASSERT_EQ(ptr, nullptr); + ASSERT_EQ(v, 123); +} + +TEST(ProxyRttiTests, TestIndirectTypeid_Void) { + pro::proxy p; + ASSERT_EQ(proxy_typeid(*p), typeid(void)); +} + +TEST(ProxyRttiTests, TestIndirectTypeid_Value) { + int a = 123; + pro::proxy p = &a; + ASSERT_EQ(proxy_typeid(*p), typeid(int)); +} + +TEST(ProxyRttiTests, TestDirectCast_Void_Fail) { + pro::proxy p; + bool exception_thrown = false; + try { + proxy_cast(p); + } catch (const pro::bad_proxy_cast&) { + exception_thrown = true; + } + ASSERT_TRUE(exception_thrown); +} + +TEST(ProxyRttiTests, TestDirectCast_Ref_Succeed) { + int v = 123; + pro::proxy p = &v; + *proxy_cast(p) = 456; + ASSERT_EQ(v, 456); +} + +TEST(ProxyRttiTests, TestDirectCast_Ref_Fail) { + int v = 123; + pro::proxy p = &v; + bool exception_thrown = false; + try { + proxy_cast(p); + } catch (const pro::bad_proxy_cast&) { + exception_thrown = true; + } + ASSERT_TRUE(exception_thrown); +} + +TEST(ProxyRttiTests, TestDirectCast_ConstRef_Succeed) { + int v = 123; + pro::proxy p = &v; + int* const& r = proxy_cast(p); + ASSERT_EQ(&v, r); + ASSERT_EQ(v, 123); +} + +TEST(ProxyRttiTests, TestDirectCast_ConstRef_Fail) { + int v = 123; + pro::proxy p = &v; + bool exception_thrown = false; + try { + proxy_cast(p); + } catch (const pro::bad_proxy_cast&) { + exception_thrown = true; + } + ASSERT_TRUE(exception_thrown); +} + +TEST(ProxyRttiTests, TestDirectCast_Copy_Succeed) { + int v1 = 123; + pro::proxy p = &v1; + int* v2 = proxy_cast(p); + ASSERT_EQ(v2, &v1); + ASSERT_EQ(v1, 123); +} + +TEST(ProxyRttiTests, TestDirectCast_Copy_Fail) { + int v = 123; + pro::proxy p = &v; + bool exception_thrown = false; + try { + proxy_cast(p); + } catch (const pro::bad_proxy_cast&) { + exception_thrown = true; + } + ASSERT_TRUE(exception_thrown); +} + +TEST(ProxyRttiTests, TestDirectCast_Move_Succeed) { + int v1 = 123; + pro::proxy p = &v1; + auto v2 = proxy_cast(std::move(p)); + ASSERT_EQ(v2, &v1); + ASSERT_EQ(v1, 123); + ASSERT_FALSE(p.has_value()); +} + +TEST(ProxyRttiTests, TestDirectCast_Move_Fail) { + int v1 = 123; + pro::proxy p = &v1; + bool exception_thrown = false; + try { + proxy_cast(std::move(p)); + } catch (const pro::bad_proxy_cast&) { + exception_thrown = true; + } + ASSERT_TRUE(exception_thrown); + ASSERT_FALSE(p.has_value()); +} + +TEST(ProxyRttiTests, TestDirectCast_Ptr_Succeed) { + int v = 123; + pro::proxy p = &v; + auto ptr = proxy_cast(&p); + static_assert(std::is_same_v); + ASSERT_EQ(*ptr, &v); + ASSERT_EQ(v, 123); +} + +TEST(ProxyRttiTests, TestDirectCast_Ptr_Fail) { + int v = 123; + pro::proxy p = &v; + auto ptr = proxy_cast(&p); + static_assert(std::is_same_v); + ASSERT_EQ(ptr, nullptr); + ASSERT_EQ(v, 123); +} + +TEST(ProxyRttiTests, TestDirectCast_ConstPtr_Succeed) { + int v = 123; + pro::proxy p = &v; + auto ptr = proxy_cast(&p); + static_assert(std::is_same_v); + ASSERT_EQ(*ptr, &v); + ASSERT_EQ(v, 123); +} + +TEST(ProxyRttiTests, TestDirectCast_ConstPtr_Fail) { + int v = 123; + pro::proxy p = &v; + auto ptr = proxy_cast(&*p); + static_assert(std::is_same_v); + ASSERT_EQ(ptr, nullptr); + ASSERT_EQ(v, 123); +} + +TEST(ProxyRttiTests, TestDirectTypeid_Void) { + pro::proxy p; + ASSERT_EQ(proxy_typeid(p), typeid(void)); +} + +TEST(ProxyRttiTests, TestDirectTypeid_Value) { + int a = 123; + pro::proxy p = &a; + ASSERT_EQ(proxy_typeid(p), typeid(int*)); +}