Skip to content

Commit

Permalink
Implement pointer-to-member support (#111)
Browse files Browse the repository at this point in the history
  • Loading branch information
mingxwa authored Jun 6, 2024
1 parent ddd4ef0 commit e60902c
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 20 deletions.
78 changes: 63 additions & 15 deletions proxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -204,10 +204,11 @@ inline void destruction_default_dispatcher(std::byte*) noexcept {}

template <bool NE, class R, class... Args>
struct overload_traits_impl : applicable_traits {
using dispatcher_type = func_ptr_t<NE, R, const std::byte*, Args...>;
template <class D>
struct meta_provider {
template <class P>
static constexpr func_ptr_t<NE, R, const std::byte*, Args...> get() {
static constexpr dispatcher_type get() {
if constexpr (invocable_dispatch<
D, NE, R, typename ptr_traits<P>::target_type&, Args...>) {
return &invocation_dispatcher_ref<P, D, R, Args...>;
Expand All @@ -223,6 +224,8 @@ struct overload_traits_impl : applicable_traits {
static constexpr bool applicable_ptr =
invocable_dispatch_ptr<D, P, NE, R, Args...>;
static constexpr bool is_noexcept = NE;
template <class... Args2>
static constexpr bool matches = std::is_invocable_v<resolver, Args2...>;
};
template <class O> struct overload_traits : inapplicable_traits {};
template <class R, class... Args>
Expand Down Expand Up @@ -492,11 +495,8 @@ struct meta_ptr<M> : M {

template <class F>
struct proxy_helper {
template <class M>
static const M& get_meta(const proxy<F>& p) noexcept
{ return *static_cast<const M*>(p.meta_.operator->()); }
static inline const std::byte* get_ptr(const proxy<F>& p) noexcept
{ return p.ptr_; }
static inline const auto& get_meta(const proxy<F>& p) noexcept
{ return *p.meta_.operator->(); }
};
template <class F, class D, class... Args>
using proxy_overload = typename facade_traits<
Expand All @@ -516,6 +516,7 @@ class proxy : public details::facade_traits<F>::base {
friend struct details::proxy_helper<F>;
using Traits = details::facade_traits<F>;
static_assert(Traits::applicable);
using Meta = typename Traits::meta;

template <class P, class... Args>
static constexpr bool HasNothrowPolyConstructor = std::conditional_t<
Expand Down Expand Up @@ -558,6 +559,46 @@ class proxy : public details::facade_traits<F>::base {
static constexpr bool HasMoveAssignment = HasMoveConstructor && HasDestructor;

public:
template <class O>
class dispatch_ptr {
using OverloadTraits = details::overload_traits<O>;
using DispatcherType = typename OverloadTraits::dispatcher_type;

public:
constexpr dispatch_ptr() noexcept = default;
constexpr dispatch_ptr(const dispatch_ptr&) noexcept = default;
dispatch_ptr& operator=(const dispatch_ptr&) noexcept = default;

#if defined(_MSC_VER) && !defined(__clang__)
template <class D>
constexpr explicit dispatch_ptr(std::in_place_type_t<D>) noexcept
: offset_(offsetof(Meta, template dispatcher_meta<typename
OverloadTraits::template meta_provider<D>>::dispatcher)) {}
DispatcherType get_dispatcher(const Meta& meta) const noexcept {
return *reinterpret_cast<const DispatcherType*>(
reinterpret_cast<const std::byte*>(&meta) + offset_);
}

private:
std::size_t offset_;
#else
template <class D>
constexpr explicit dispatch_ptr(std::in_place_type_t<D>) noexcept
: ptr_(&details::dispatcher_meta<typename OverloadTraits
::template meta_provider<D>>::dispatcher) {}
DispatcherType get_dispatcher(const Meta& meta) const noexcept
{ return meta.*ptr_; }

private:
DispatcherType Meta::* ptr_;
#endif // defined(_MSC_VER) && !defined(__clang__)
};
template <class D, class... Args>
static auto consteval get_dispatch_ptr()
requires(requires { typename details::proxy_overload<F, D, Args...>; }) {
return dispatch_ptr<details::proxy_overload<F, D, Args...>>{
std::in_place_type<D>};
}
proxy() noexcept = default;
proxy(std::nullptr_t) noexcept : proxy() {}
proxy(const proxy& rhs) noexcept(HasNothrowCopyConstructor)
Expand Down Expand Up @@ -694,15 +735,25 @@ class proxy : public details::facade_traits<F>::base {
return initialize<P>(il, std::forward<Args>(args)...);
}

template <class O>
friend auto operator->*(const proxy& p, dispatch_ptr<O> ptd) noexcept {
return [&p, ptd]<class... Args>(Args&&... args)
noexcept(details::overload_traits<O>::is_noexcept) -> decltype(auto)
requires(details::overload_traits<O>::template matches<Args...>) {
return ptd.get_dispatcher(*p.meta_.operator->())(
p.ptr_, std::forward<Args>(args)...);
};
}

private:
template <class P, class... Args>
P& initialize(Args&&... args) {
std::construct_at(reinterpret_cast<P*>(ptr_), std::forward<Args>(args)...);
meta_ = details::meta_ptr<typename Traits::meta>{std::in_place_type<P>};
meta_ = details::meta_ptr<Meta>{std::in_place_type<P>};
return *std::launder(reinterpret_cast<P*>(ptr_));
}

details::meta_ptr<typename Traits::meta> meta_;
details::meta_ptr<Meta> meta_;
alignas(F::constraints.max_align) std::byte ptr_[F::constraints.max_size];
};

Expand All @@ -711,18 +762,14 @@ decltype(auto) proxy_invoke(const proxy<F>& p, Args&&... args)
noexcept(details::overload_traits<details::proxy_overload<F, D, Args...>>
::is_noexcept)
requires(requires { typename details::proxy_overload<F, D, Args...>; }) {
return details::proxy_helper<F>
::template get_meta<details::dispatcher_meta<
typename details::overload_traits<details::proxy_overload<
F, D, Args...>>::template meta_provider<D>>>(p)
.dispatcher(details::proxy_helper<F>::get_ptr(p),
std::forward<Args>(args)...);
constexpr auto ptd = proxy<F>::template get_dispatch_ptr<D, Args...>();
return (p->*ptd)(std::forward<Args>(args)...);
}

template <class R, class F>
const R& proxy_reflect(const proxy<F>& p) noexcept
requires(details::facade_traits<F>::template has_refl<R>)
{ return details::proxy_helper<F>::template get_meta<R>(p); }
{ return details::proxy_helper<F>::get_meta(p); }

namespace details {

Expand Down Expand Up @@ -1282,6 +1329,7 @@ ___PRO_OPERATOR_DISPATCH_TRAITS_ASSIGNMENT_IMPL(^=)
___PRO_OPERATOR_DISPATCH_TRAITS_ASSIGNMENT_IMPL(<<=)
___PRO_OPERATOR_DISPATCH_TRAITS_ASSIGNMENT_IMPL(>>=)
___PRO_OPERATOR_DISPATCH_TRAITS_BINARY_IMPL(,)
___PRO_OPERATOR_DISPATCH_TRAITS_BINARY_IMPL(->*)

#undef ___PRO_OPERATOR_DISPATCH_TRAITS_EXTENDED_BINARY_IMPL
#undef ___PRO_OPERATOR_DISPATCH_TRAITS_BINARY_IMPL
Expand Down
50 changes: 45 additions & 5 deletions tests/proxy_dispatch_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
// Licensed under the MIT License.

#include <gtest/gtest.h>
#ifdef _MSC_VER
#include <vector>
#if defined(_MSC_VER) && !defined(__clang__)
#pragma warning(push)
#pragma warning(disable: 4834) // False alarm from MSVC: warning C4834: discarding return value of function with [[nodiscard]] attribute
#endif // _MSC_VER
#endif // defined(_MSC_VER) && !defined(__clang__)
#include "proxy.h"
#ifdef _MSC_VER
#if defined(_MSC_VER) && !defined(__clang__)
#pragma warning(pop)
#endif // _MSC_VER
#endif // defined(_MSC_VER) && !defined(__clang__)

namespace {

Expand Down Expand Up @@ -46,6 +47,7 @@ PRO_DEF_OPERATOR_DISPATCH(OpBitwiseXorAssignment, "^=");
PRO_DEF_OPERATOR_DISPATCH(OpLeftShiftAssignment, "<<=");
PRO_DEF_OPERATOR_DISPATCH(OpRightShiftAssignment, ">>=");
PRO_DEF_OPERATOR_DISPATCH(OpComma, ",");
PRO_DEF_OPERATOR_DISPATCH(OpPtrToMem, "->*");
PRO_DEF_OPERATOR_DISPATCH(OpArrow, "->");
PRO_DEF_OPERATOR_DISPATCH(OpParentheses, "()");
PRO_DEF_OPERATOR_DISPATCH(OpBrackets, "[]");
Expand All @@ -72,19 +74,28 @@ PRO_DEF_PREFIX_OPERATOR_DISPATCH(PreOpCaret, "^");
PRO_DEF_PREFIX_OPERATOR_DISPATCH(PreOpLeftShift, "<<");
PRO_DEF_PREFIX_OPERATOR_DISPATCH(PreOpRightShift, ">>");
PRO_DEF_PREFIX_OPERATOR_DISPATCH(PreOpComma, ",");
PRO_DEF_PREFIX_OPERATOR_DISPATCH(PreOpPtrToMem, "->*");

PRO_DEF_CONVERTION_DISPATCH(ConvertToInt, int);

struct CommaTester {
public:
explicit CommaTester(int v) : value_(v) {}
int operator,(int v) { return value_ + v; }
friend int operator,(int v, CommaTester c) { return v * c.value_; }
friend int operator,(int v, CommaTester self) { return v * self.value_; }

private:
int value_;
};

struct PtrToMemTester {
public:
explicit PtrToMemTester(int v) : value_(v) {}
friend int operator->*(int v, PtrToMemTester self) { return v * self.value_; }

private:
int value_;
};
} // namespace

TEST(ProxyDispatchTests, TestOpPlus) {
Expand Down Expand Up @@ -340,6 +351,28 @@ TEST(ProxyDispatchTests, TestOpComma) {
ASSERT_EQ((p, 6), 9);
}

TEST(ProxyDispatchTests, TestOpPtrToMem) {
struct Base1 { int a; int b; int c; };
struct Base2 { double x; };
struct Derived1 : Base1 { int x; };
struct Derived2 : Base2, Base1 { int d; };
struct TestFacade : pro::facade_builder::add_convention<OpPtrToMem, int&(int Base1::* ptm)>::build {};
Derived1 v1{};
Derived2 v2{};
pro::proxy<TestFacade> p1 = &v1, p2 = &v2;
std::vector<int Base1::*> fields{&Base1::a, &Base1::b, &Base1::c};
for (int i = 0; i < std::ssize(fields); ++i) {
p1->*fields[i] = i + 1;
p2->*fields[i] = i + 1;
}
ASSERT_EQ(v1.a, 1);
ASSERT_EQ(v1.b, 2);
ASSERT_EQ(v1.c, 3);
ASSERT_EQ(v2.a, 1);
ASSERT_EQ(v2.b, 2);
ASSERT_EQ(v2.c, 3);
}

TEST(ProxyDispatchTests, TestOpArrow) {
struct TestFacade : pro::facade_builder::add_convention<OpArrow, const void*()>::build {};
int v = 12;
Expand Down Expand Up @@ -539,6 +572,13 @@ TEST(ProxyDispatchTests, TestPreOpComma) {
ASSERT_EQ((7, p), 21);
}

TEST(ProxyDispatchTests, TestPreOpPtrToMem) {
struct TestFacade : pro::facade_builder::add_convention<PreOpPtrToMem, int(int val)>::build {};
PtrToMemTester v{3};
pro::proxy<TestFacade> p = &v;
ASSERT_EQ(2->*p, 6);
}

TEST(ProxyDispatchTests, TestConvertion) {
struct TestFacade : pro::facade_builder::add_convention<ConvertToInt, int()>::build {};
double v = 12.3;
Expand Down
10 changes: 10 additions & 0 deletions tests/proxy_invocation_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -314,3 +314,13 @@ TEST(ProxyInvocationTests, TestObserverDispatch) {
ASSERT_EQ(ToString(p), "123");
}

TEST(ProxyInvocationTests, TestPtrToMember) {
using Runnable = pro::proxy<spec::Callable<void()>>;
constexpr auto OpCallPtr = Runnable::get_dispatch_ptr<spec::OpCall>();
static_assert(std::is_same_v<decltype(OpCallPtr), const Runnable::dispatch_ptr<void()>>);
int side_effects = 0;
auto f = [&] { side_effects = 1; };
Runnable p = &f;
(p->*OpCallPtr)();
ASSERT_EQ(side_effects, 1);
}

0 comments on commit e60902c

Please sign in to comment.