// Copyright (c) 2022-2026 Microsoft Corporation. // Copyright (c) 2026-Present Next Gen C++ Foundation. // Licensed under the MIT License. #include #include #include #include #include #include #include #include #include #include #include #include #include #include "utils.h" namespace proxy_invocation_tests_detail { template struct MovableCallable : pro::facade_builder // ::add_convention, Os...> // ::build {}; template struct Callable : pro::facade_builder // ::support_copy // ::add_facade> // ::build {}; template struct WeakCallable : pro::facade_builder // ::support_copy // ::add_convention>, Os...> // ::build {}; PRO_DEF_FREE_DISPATCH(FreeSize, std::ranges::size, Size); PRO_DEF_FREE_DISPATCH(FreeForEach, std::ranges::for_each, ForEach); template struct Iterable : pro::facade_builder // ::add_convention)> // ::template add_convention // ::build {}; template struct Container; template pro::proxy> AppendImpl(C& container, T&& v) { container.push_back(std::move(v)); return &container; } PRO_DEF_FREE_DISPATCH(FreeAppend, AppendImpl, Append); template struct FreeAppendOverloadTraits { template using Type = pro::proxy(T) const&; }; template struct Container : pro::facade_builder // ::add_facade> // ::template add_convention< FreeAppend, pro::facade_aware_overload_t< FreeAppendOverloadTraits::template Type>> // ::build {}; PRO_DEF_MEM_DISPATCH(MemAt, at, at); struct ResourceDictionary : pro::facade_builder // ::add_convention, std::string(int)> // ::build {}; template pro::proxy LockImpl(const std::weak_ptr& p) { auto result = p.lock(); if (static_cast(result)) { return result; } return nullptr; } template PRO_DEF_FREE_DISPATCH(FreeLock, LockImpl, Lock); template struct Weak : pro::facade_builder // ::support_copy // ::add_convention, pro::proxy()> // ::build {}; template pro::proxy> GetWeakImpl(const std::shared_ptr& p) { return pro::make_proxy, std::weak_ptr>(p); } template pro::proxy> GetWeakImpl(T&&) { return nullptr; } template PRO_DEF_FREE_DISPATCH(FreeGetWeak, GetWeakImpl, GetWeak); template using FreeGetWeakOverload = pro::proxy>() const&; struct SharedStringable : pro::facade_builder // ::add_facade // ::add_direct_convention< FreeGetWeak, pro::facade_aware_overload_t> // ::build {}; template concept CallableFacade = requires(pro::proxy p, Args... args) { { (*p)(std::forward(args)...) }; typename std::enable_if_t(args)...))>; }; // Static assertions for facade Callable static_assert(!CallableFacade, false, std::nullptr_t>); // Wrong arguments static_assert(CallableFacade, false, float>); // Invoking without specifying a dispatch static_assert(CallableFacade, true, int>); // Invoking noexcept overloads static_assert(CallableFacade, false, double>); // Invoking overloads that may throw template std::vector GetTypeIndices() { return {std::type_index{typeid(Args)}...}; } template std::string Dump(T&& value) noexcept { std::ostringstream out; out << std::boolalpha << "is_const=" << std::is_const_v> << ", is_ref=" << std::is_lvalue_reference_v << ", value=" << value; return std::move(out).str(); } PRO_DEF_FREE_DISPATCH(FreeDump, Dump); PRO_DEF_FREE_DISPATCH(FreeInvoke, std::invoke, Invoke); PRO_DEF_FREE_AS_MEM_DISPATCH(MemInvoke, std::invoke, Invoke); } // namespace proxy_invocation_tests_detail namespace detail = proxy_invocation_tests_detail; TEST(ProxyInvocationTests, TestArgumentForwarding) { std::string arg1 = "My string"; std::vector arg2 = {1, 2, 3}; std::vector arg2_copy = arg2; std::string arg1_received; std::vector arg2_received; int expected_result = 456; auto f = [&](std::string&& s, std::vector&& v) -> int { arg1_received = std::move(s); arg2_received = std::move(v); return expected_result; }; pro::proxy)>> p = &f; int result = (*p)(arg1, std::move(arg2)); ASSERT_TRUE(p.has_value()); ASSERT_EQ(arg1_received, arg1); ASSERT_TRUE(arg2.empty()); ASSERT_EQ(arg2_received, arg2_copy); ASSERT_EQ(result, expected_result); } TEST(ProxyInvocationTests, TestThrow) { const char* expected_error_message = "My exception"; auto f = [&] { throw std::runtime_error{expected_error_message}; }; bool exception_thrown = false; pro::proxy> p = &f; try { (*p)(); } catch (const std::runtime_error& e) { exception_thrown = true; ASSERT_STREQ(e.what(), expected_error_message); } ASSERT_TRUE(exception_thrown); ASSERT_TRUE(p.has_value()); } TEST(ProxyInvocationTests, TestMultipleDispatches_Unique) { std::list l = {1, 2, 3}; pro::proxy> p = &l; ASSERT_EQ(Size(*p), std::size_t{3}); int sum = 0; auto accumulate_sum = [&](int x) { sum += x; }; ForEach(*p, accumulate_sum); ASSERT_EQ(sum, 6); } TEST(ProxyInvocationTests, TestMultipleDispatches_Duplicated) { struct DuplicatedIterable : pro::facade_builder // ::add_convention)> // ::add_convention // ::add_convention)> // ::build {}; static_assert( sizeof(pro::detail::facade_traits::meta) == sizeof(pro::detail::facade_traits>::meta)); std::list l = {1, 2, 3}; pro::proxy p = &l; ASSERT_EQ(Size(*p), std::size_t{3}); int sum = 0; auto accumulate_sum = [&](int x) { sum += x; }; ForEach(*p, accumulate_sum); ASSERT_EQ(sum, 6); } TEST(ProxyInvocationTests, TestRecursiveDefinition) { std::list l = {1, 2, 3}; pro::proxy> p = &l; ASSERT_EQ(Size(*p), std::size_t{3}); int sum = 0; auto accumulate_sum = [&](int x) { sum += x; }; ForEach(*p, accumulate_sum); ASSERT_EQ(sum, 6); Append(*Append(*Append(*p, 4), 5), 6); ASSERT_EQ(Size(*p), std::size_t{6}); sum = 0; ForEach(*p, accumulate_sum); ASSERT_EQ(sum, 21); } TEST(ProxyInvocationTests, TestOverloadResolution_Member) { struct OverloadedCallable : pro::facade_builder // ::add_convention, void(int), void(double), void(const char*), void(char*), void(std::string, int)> // ::build {}; std::vector side_effect; auto p = pro::make_proxy([&](auto&&... args) { side_effect = detail::GetTypeIndices...>(); }); (*p)(123); ASSERT_EQ(side_effect, detail::GetTypeIndices()); (*p)(1.23); ASSERT_EQ(side_effect, detail::GetTypeIndices()); char foo[2]; (*p)(foo); ASSERT_EQ(side_effect, detail::GetTypeIndices()); (*p)("lalala"); ASSERT_EQ(side_effect, detail::GetTypeIndices()); (*p)("lalala", 0); ASSERT_EQ(side_effect, (detail::GetTypeIndices())); ASSERT_FALSE((std::is_invocable_v>)); } TEST(ProxyInvocationTests, TestOverloadResolution_Free) { struct OverloadedCallable : pro::facade_builder // ::add_convention // ::build {}; std::vector side_effect; auto p = pro::make_proxy([&](auto&&... args) { side_effect = detail::GetTypeIndices...>(); }); Invoke(*p, 123); ASSERT_EQ(side_effect, detail::GetTypeIndices()); Invoke(*p, 1.23); ASSERT_EQ(side_effect, detail::GetTypeIndices()); char foo[2]; Invoke(*p, foo); ASSERT_EQ(side_effect, detail::GetTypeIndices()); Invoke(*p, "lalala"); ASSERT_EQ(side_effect, detail::GetTypeIndices()); Invoke(*p, "lalala", 0); ASSERT_EQ(side_effect, (detail::GetTypeIndices())); } TEST(ProxyInvocationTests, TestOverloadResolution_FreeAsMem) { struct OverloadedInvocable : pro::facade_builder // ::add_convention // ::build {}; std::vector side_effect; auto p = pro::make_proxy([&](auto&&... args) { side_effect = detail::GetTypeIndices...>(); }); p->Invoke(123); ASSERT_EQ(side_effect, detail::GetTypeIndices()); p->Invoke(1.23); ASSERT_EQ(side_effect, detail::GetTypeIndices()); char foo[2]; p->Invoke(foo); ASSERT_EQ(side_effect, detail::GetTypeIndices()); p->Invoke("lalala"); ASSERT_EQ(side_effect, detail::GetTypeIndices()); p->Invoke("lalala", 0); ASSERT_EQ(side_effect, (detail::GetTypeIndices())); } TEST(ProxyInvocationTests, TestNoexcept) { std::vector side_effect; auto p = pro::make_proxy>( [&](auto&&... args) noexcept { side_effect = detail::GetTypeIndices...>(); }); static_assert(noexcept((*p)(123))); (*p)(123); ASSERT_EQ(side_effect, detail::GetTypeIndices()); static_assert(!noexcept((*p)(1.23))); (*p)(1.23); ASSERT_EQ(side_effect, detail::GetTypeIndices()); ASSERT_FALSE((std::is_invocable_v)); } TEST(ProxyInvocationTests, TestFunctionPointer) { struct TestFacade : detail::Callable()> {}; pro::proxy p{&detail::GetTypeIndices}; auto ret = (*p)(); ASSERT_EQ(ret, (detail::GetTypeIndices())); } TEST(ProxyInvocationTests, TestMemberDispatchDefault) { std::vector container1{"hello", "world", "!"}; std::list container2{"hello", "world"}; pro::proxy p = &container1; ASSERT_EQ(p->at(0), "hello"); p = &container2; { bool exception_thrown = false; try { p->at(0); } catch (const pro::not_implemented&) { exception_thrown = true; } ASSERT_TRUE(exception_thrown); } } TEST(ProxyInvocationTests, TestFreeDispatchDefault) { { int side_effect = 0; auto p = pro::make_proxy>([&] { side_effect = 1; }); (*p)(); ASSERT_EQ(side_effect, 1); } { bool exception_thrown = false; auto p = pro::make_proxy>(123); try { (*p)(); } catch (const pro::not_implemented&) { exception_thrown = true; } ASSERT_TRUE(exception_thrown); } } TEST(ProxyInvocationTests, TestObserverDispatch) { int test_val = 123; pro::proxy p{std::make_shared(test_val)}; auto weak = GetWeak(p); ASSERT_TRUE(weak.has_value()); { auto locked = Lock(*weak); ASSERT_TRUE(locked.has_value()); ASSERT_EQ(ToString(*locked), "123"); } p = &test_val; // The underlying std::shared_ptr will be destroyed ASSERT_TRUE(weak.has_value()); ASSERT_FALSE(Lock(*weak).has_value()); ASSERT_FALSE(GetWeak(p).has_value()); ASSERT_EQ(ToString(*p), "123"); } TEST(ProxyInvocationTests, TestQualifiedConvention_Member) { struct TestFacade : pro::facade_builder // ::add_convention, int() &, int() const&, int() && noexcept, int() const&&> // ::build {}; struct TestCallable { int operator()() & noexcept { return 0; } int operator()() const& noexcept { return 1; } int operator()() && noexcept { return 2; } int operator()() const&& noexcept { return 3; } }; pro::proxy p = pro::make_proxy(); static_assert(!noexcept((*p)())); static_assert(noexcept((*std::move(p))())); ASSERT_EQ((*p)(), 0); ASSERT_EQ((*std::as_const(p))(), 1); ASSERT_EQ((*std::move(p))(), 2); p = pro::make_proxy(); ASSERT_EQ((*std::move(std::as_const(p)))(), 3); } TEST(ProxyInvocationTests, TestQualifiedConvention_Free) { struct TestFacade : pro::facade_builder // ::add_convention // ::build {}; pro::proxy p = pro::make_proxy(123); static_assert(!noexcept(Dump(*p))); static_assert(noexcept(Dump(*std::move(p)))); ASSERT_EQ(Dump(*p), "is_const=false, is_ref=true, value=123"); ASSERT_EQ(Dump(*std::as_const(p)), "is_const=true, is_ref=true, value=123"); ASSERT_EQ(Dump(*std::move(p)), "is_const=false, is_ref=false, value=123"); p = pro::make_proxy(123); ASSERT_EQ(Dump(*std::move(std::as_const(p))), "is_const=true, is_ref=false, value=123"); }