Skip to content

Commit

Permalink
Add macros to simplify syntax of creating dispatches and facades (#46)
Browse files Browse the repository at this point in the history
* Update macro

* Fix build error

* Resolve comments
  • Loading branch information
mingxwa authored Nov 30, 2023
1 parent d8dbbb0 commit 1937e4b
Show file tree
Hide file tree
Showing 8 changed files with 169 additions and 145 deletions.
28 changes: 14 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,17 @@ The "proxy" is a single-header, cross-platform C++ library that Microsoft uses t

The "proxy" is a header-only C++20 library. Once you set the language level of your compiler not earlier than C++20 and get the header file ([proxy.h](proxy.h)), you are all set. You can also install the library via [vcpkg](https://github.com/microsoft/vcpkg/), which is a C++ library manager invented by Microsoft, by searching for "proxy" (see [vcpkg.info](https://vcpkg.info/port/proxy)).

All the facilities of the library are defined in namespace `pro`. The 3 major class templates are `dispatch`, `facade` and `proxy`. Here is a demo showing how to use this library to implement runtime polymorphism in a different way from the traditional inheritance-based approach:
All the facilities of the library are defined in namespace `pro`. The 3 major class templates are `dispatch`, `facade` and `proxy`. Some macros are defined (currently not in the proposal of standardization) to facilitate definition of `dispatch`es and `facade`s. Here is a demo showing how to use this library to implement runtime polymorphism in a different way from the traditional inheritance-based approach:

```cpp
// Abstraction
struct Draw : pro::dispatch<void(std::ostream&)> {
void operator()(const auto& self, std::ostream& out) { self.Draw(out); }
};
struct Area : pro::dispatch<double()> {
double operator()(const auto& self) { return self.Area(); }
};
struct DrawableFacade : pro::facade<Draw, Area> {};
// Abstraction (poly is short for polymorphism)
namespace poly {

DEFINE_MEMBER_DISPATCH(Draw, Draw, void(std::ostream&));
DEFINE_MEMBER_DISPATCH(Area, Area, double());
DEFINE_FACADE(Drawable, Draw, Area);

} // namespace poly

// Implementation
class Rectangle {
Expand All @@ -43,20 +43,20 @@ class Rectangle {
};

// Client - Consumer
std::string PrintDrawableToString(pro::proxy<DrawableFacade> p) {
std::string PrintDrawableToString(pro::proxy<poly::Drawable> p) {
std::stringstream result;
result << "shape = ";
p.invoke<Draw>(result);
result << ", area = " << p.invoke<Area>();
p.invoke<poly::Draw>(result);
result << ", area = " << p.invoke<poly::Area>();
return std::move(result).str();
}

// Client - Producer
pro::proxy<DrawableFacade> CreateRectangleAsDrawable(int width, int height) {
pro::proxy<poly::Drawable> CreateRectangleAsDrawable(int width, int height) {
Rectangle rect;
rect.SetWidth(width);
rect.SetHeight(height);
return pro::make_proxy<DrawableFacade>(rect);
return pro::make_proxy<poly::Drawable>(rect);
}
```
Expand Down
73 changes: 50 additions & 23 deletions proxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,6 @@ namespace pro {

enum class constraint_level { none, nontrivial, nothrow, trivial };

template <class... Os>
struct dispatch { using overload_types = std::tuple<Os...>; };
template <auto CPO, class... Os>
struct dispatch_adaptor : dispatch<Os...> {
template <class T, class... Args>
requires(std::is_invocable_v<decltype(CPO)&, T, Args...>)
constexpr decltype(auto) operator()(T&& value, Args&&... args) const
{ return CPO(std::forward<T>(value), std::forward<Args>(args)...); }
};

template <class... Ds>
struct facade {
using dispatch_types = std::tuple<Ds...>;
using reflection_type = void;
static constexpr std::size_t maximum_size = sizeof(void*) * 2u;
static constexpr std::size_t maximum_alignment = alignof(void*);
static constexpr auto minimum_copyability = constraint_level::none;
static constexpr auto minimum_relocatability = constraint_level::nothrow;
static constexpr auto minimum_destructibility = constraint_level::nothrow;
facade() = delete;
};

namespace details {

struct applicable_traits { static constexpr bool applicable = true; };
Expand Down Expand Up @@ -123,7 +101,11 @@ struct overload_traits<R(Args...)> : applicable_traits {
{ { D{}(std::forward<T>(operand), std::forward<Args>(args)...) }; };
template <class D, class P>
static R dispatcher(const char* p, Args... args) {
return D{}(**reinterpret_cast<const P*>(p), std::forward<Args>(args)...);
if constexpr (std::is_void_v<R>) {
D{}(**reinterpret_cast<const P*>(p), std::forward<Args>(args)...);
} else {
return D{}(**reinterpret_cast<const P*>(p), std::forward<Args>(args)...);
}
}
};

Expand Down Expand Up @@ -566,6 +548,51 @@ proxy<F> make_proxy(T&& value) {
return details::make_proxy_impl<F, std::decay_t<T>>(std::forward<T>(value));
}

template <class... Os>
struct dispatch { using overload_types = std::tuple<Os...>; };

template <class... Ds>
struct facade {
using dispatch_types = std::tuple<Ds...>;
using reflection_type = void;
static constexpr std::size_t maximum_size = sizeof(void*) * 2u;
static constexpr std::size_t maximum_alignment = alignof(void*);
static constexpr auto minimum_copyability = constraint_level::none;
static constexpr auto minimum_relocatability = constraint_level::nothrow;
static constexpr auto minimum_destructibility = constraint_level::nothrow;
facade() = delete;
};

} // namespace pro

// The following macros facilitate definition of dispatch and facade types
#define DEFINE_MEMBER_DISPATCH(__NAME, __FUNC, ...) \
struct __NAME : ::pro::dispatch<__VA_ARGS__> { \
template <class __T, class... __Args> \
decltype(auto) operator()(__T&& __self, __Args&&... __args) \
requires(requires{ std::forward<__T>(__self) \
.__FUNC(std::forward<__Args>(__args)...); }) { \
return std::forward<__T>(__self) \
.__FUNC(std::forward<__Args>(__args)...); \
} \
}
#define DEFINE_FREE_DISPATCH(__NAME, __FUNC, ...) \
struct __NAME : ::pro::dispatch<__VA_ARGS__> { \
template <class __T, class... __Args> \
decltype(auto) operator()(__T&& __self, __Args&&... __args) \
requires(requires{ __FUNC(std::forward<__T>(__self), \
std::forward<__Args>(__args)...); }) { \
return __FUNC(std::forward<__T>(__self), \
std::forward<__Args>(__args)...); \
} \
}

#define DEFINE_FACADE(__NAME, ...) \
struct __NAME : ::pro::facade<__VA_ARGS__> {}
#define DEFINE_COPYABLE_FACADE(__NAME, ...) \
struct __NAME : ::pro::facade<__VA_ARGS__> { \
static constexpr auto minimum_copyability = \
pro::constraint_level::nontrivial; \
}

#endif // _MSFT_PROXY_
12 changes: 7 additions & 5 deletions samples/resource_dictionary/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@

#include <proxy/proxy.h>

struct at : pro::dispatch<std::string(int)> {
auto operator()(const auto& self, int key) { return self.at(key); }
};
struct resource_dictionary : pro::facade<at> {};
namespace poly {

void demo_print(pro::proxy<resource_dictionary> dictionary) {
DEFINE_MEMBER_DISPATCH(At, at, std::string(int));
DEFINE_FACADE(Dictionary, At);

} // namespace poly

void demo_print(pro::proxy<poly::Dictionary> dictionary) {
std::cout << dictionary(1) << std::endl;
}

Expand Down
68 changes: 36 additions & 32 deletions tests/proxy_creation_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,32 @@

namespace {

struct TraitsReflection {
namespace poly {

struct SboObserver {
public:
template <class P>
constexpr explicit TraitsReflection(std::in_place_type_t<pro::details::sbo_ptr<P>>)
: sbo_enabled_(true) {}
constexpr explicit SboObserver(std::in_place_type_t<pro::details::sbo_ptr<P>>)
: SboEnabled(true) {}
template <class P>
constexpr explicit TraitsReflection(std::in_place_type_t<pro::details::deep_ptr<P>>)
: sbo_enabled_(false) {}
constexpr explicit SboObserver(std::in_place_type_t<P>)
: SboEnabled(false) {}

bool sbo_enabled_;
bool SboEnabled;
};

struct TestSmallFacade : pro::facade<utils::ToString> {
using reflection_type = TraitsReflection;
struct TestSmallStringable : pro::facade<utils::poly::ToString> {
using reflection_type = SboObserver;
static constexpr std::size_t maximum_size = sizeof(void*);
static constexpr auto minimum_copyability = pro::constraint_level::nontrivial;
};
struct TestLargeFacade : pro::facade<utils::ToString> {
using reflection_type = TraitsReflection;
struct TestLargeStringable : pro::facade<utils::poly::ToString> {
using reflection_type = SboObserver;
static constexpr auto minimum_copyability = pro::constraint_level::nontrivial;
};

} // namespace poly

} // namespace

TEST(ProxyCreationTests, TestMakeProxy_WithSBO_FromValue) {
Expand All @@ -37,10 +41,10 @@ TEST(ProxyCreationTests, TestMakeProxy_WithSBO_FromValue) {
utils::LifetimeTracker::Session session{ &tracker };
expected_ops.emplace_back(1, utils::LifetimeOperationType::kValueConstruction);
{
auto p = pro::make_proxy<TestLargeFacade>(session);
auto p = pro::make_proxy<poly::TestLargeStringable>(session);
ASSERT_TRUE(p.has_value());
ASSERT_EQ(p.invoke(), "Session 2");
ASSERT_TRUE(p.reflect().sbo_enabled_);
ASSERT_TRUE(p.reflect().SboEnabled);
expected_ops.emplace_back(2, utils::LifetimeOperationType::kCopyConstruction);
ASSERT_TRUE(tracker.GetOperations() == expected_ops);
}
Expand All @@ -52,10 +56,10 @@ TEST(ProxyCreationTests, TestMakeProxy_WithSBO_InPlace) {
utils::LifetimeTracker tracker;
std::vector<utils::LifetimeOperation> expected_ops;
{
auto p = pro::make_proxy<TestLargeFacade, utils::LifetimeTracker::Session>(&tracker);
auto p = pro::make_proxy<poly::TestLargeStringable, utils::LifetimeTracker::Session>(&tracker);
ASSERT_TRUE(p.has_value());
ASSERT_EQ(p.invoke(), "Session 1");
ASSERT_TRUE(p.reflect().sbo_enabled_);
ASSERT_TRUE(p.reflect().SboEnabled);
expected_ops.emplace_back(1, utils::LifetimeOperationType::kValueConstruction);
ASSERT_TRUE(tracker.GetOperations() == expected_ops);
}
Expand All @@ -67,10 +71,10 @@ TEST(ProxyCreationTests, TestMakeProxy_WithSBO_InPlaceInitializerList) {
utils::LifetimeTracker tracker;
std::vector<utils::LifetimeOperation> expected_ops;
{
auto p = pro::make_proxy<TestLargeFacade, utils::LifetimeTracker::Session>({ 1, 2, 3 }, &tracker);
auto p = pro::make_proxy<poly::TestLargeStringable, utils::LifetimeTracker::Session>({ 1, 2, 3 }, &tracker);
ASSERT_TRUE(p.has_value());
ASSERT_EQ(p.invoke(), "Session 1");
ASSERT_TRUE(p.reflect().sbo_enabled_);
ASSERT_TRUE(p.reflect().SboEnabled);
expected_ops.emplace_back(1, utils::LifetimeOperationType::kInitializerListConstruction);
ASSERT_TRUE(tracker.GetOperations() == expected_ops);
}
Expand All @@ -82,15 +86,15 @@ TEST(ProxyCreationTests, TestMakeProxy_WithSBO_Lifetime_Copy) {
utils::LifetimeTracker tracker;
std::vector<utils::LifetimeOperation> expected_ops;
{
auto p1 = pro::make_proxy<TestLargeFacade, utils::LifetimeTracker::Session>(&tracker);
auto p1 = pro::make_proxy<poly::TestLargeStringable, utils::LifetimeTracker::Session>(&tracker);
expected_ops.emplace_back(1, utils::LifetimeOperationType::kValueConstruction);
auto p2 = p1;
ASSERT_TRUE(p1.has_value());
ASSERT_EQ(p1.invoke(), "Session 1");
ASSERT_TRUE(p1.reflect().sbo_enabled_);
ASSERT_TRUE(p1.reflect().SboEnabled);
ASSERT_TRUE(p2.has_value());
ASSERT_EQ(p2.invoke(), "Session 2");
ASSERT_TRUE(p2.reflect().sbo_enabled_);
ASSERT_TRUE(p2.reflect().SboEnabled);
expected_ops.emplace_back(2, utils::LifetimeOperationType::kCopyConstruction);
ASSERT_TRUE(tracker.GetOperations() == expected_ops);
}
Expand All @@ -103,13 +107,13 @@ TEST(ProxyCreationTests, TestMakeProxy_WithSBO_Lifetime_Move) {
utils::LifetimeTracker tracker;
std::vector<utils::LifetimeOperation> expected_ops;
{
auto p1 = pro::make_proxy<TestLargeFacade, utils::LifetimeTracker::Session>(&tracker);
auto p1 = pro::make_proxy<poly::TestLargeStringable, utils::LifetimeTracker::Session>(&tracker);
expected_ops.emplace_back(1, utils::LifetimeOperationType::kValueConstruction);
auto p2 = std::move(p1);
ASSERT_FALSE(p1.has_value());
ASSERT_TRUE(p2.has_value());
ASSERT_EQ(p2.invoke(), "Session 2");
ASSERT_TRUE(p2.reflect().sbo_enabled_);
ASSERT_TRUE(p2.reflect().SboEnabled);
expected_ops.emplace_back(2, utils::LifetimeOperationType::kMoveConstruction);
expected_ops.emplace_back(1, utils::LifetimeOperationType::kDestruction);
ASSERT_TRUE(tracker.GetOperations() == expected_ops);
Expand All @@ -124,10 +128,10 @@ TEST(ProxyCreationTests, TestMakeProxy_WithoutSBO_FromValue) {
utils::LifetimeTracker::Session session{ &tracker };
expected_ops.emplace_back(1, utils::LifetimeOperationType::kValueConstruction);
{
auto p = pro::make_proxy<TestSmallFacade>(session);
auto p = pro::make_proxy<poly::TestSmallStringable>(session);
ASSERT_TRUE(p.has_value());
ASSERT_EQ(p.invoke(), "Session 2");
ASSERT_FALSE(p.reflect().sbo_enabled_);
ASSERT_FALSE(p.reflect().SboEnabled);
expected_ops.emplace_back(2, utils::LifetimeOperationType::kCopyConstruction);
ASSERT_TRUE(tracker.GetOperations() == expected_ops);
}
Expand All @@ -139,10 +143,10 @@ TEST(ProxyCreationTests, TestMakeProxy_WithoutSBO_InPlace) {
utils::LifetimeTracker tracker;
std::vector<utils::LifetimeOperation> expected_ops;
{
auto p = pro::make_proxy<TestSmallFacade, utils::LifetimeTracker::Session>(&tracker);
auto p = pro::make_proxy<poly::TestSmallStringable, utils::LifetimeTracker::Session>(&tracker);
ASSERT_TRUE(p.has_value());
ASSERT_EQ(p.invoke(), "Session 1");
ASSERT_FALSE(p.reflect().sbo_enabled_);
ASSERT_FALSE(p.reflect().SboEnabled);
expected_ops.emplace_back(1, utils::LifetimeOperationType::kValueConstruction);
ASSERT_TRUE(tracker.GetOperations() == expected_ops);
}
Expand All @@ -154,10 +158,10 @@ TEST(ProxyCreationTests, TestMakeProxy_WithoutSBO_InPlaceInitializerList) {
utils::LifetimeTracker tracker;
std::vector<utils::LifetimeOperation> expected_ops;
{
auto p = pro::make_proxy<TestSmallFacade, utils::LifetimeTracker::Session>({ 1, 2, 3 }, &tracker);
auto p = pro::make_proxy<poly::TestSmallStringable, utils::LifetimeTracker::Session>({ 1, 2, 3 }, &tracker);
ASSERT_TRUE(p.has_value());
ASSERT_EQ(p.invoke(), "Session 1");
ASSERT_FALSE(p.reflect().sbo_enabled_);
ASSERT_FALSE(p.reflect().SboEnabled);
expected_ops.emplace_back(1, utils::LifetimeOperationType::kInitializerListConstruction);
ASSERT_TRUE(tracker.GetOperations() == expected_ops);
}
Expand All @@ -169,15 +173,15 @@ TEST(ProxyCreationTests, TestMakeProxy_WithoutSBO_Lifetime_Copy) {
utils::LifetimeTracker tracker;
std::vector<utils::LifetimeOperation> expected_ops;
{
auto p1 = pro::make_proxy<TestSmallFacade, utils::LifetimeTracker::Session>(&tracker);
auto p1 = pro::make_proxy<poly::TestSmallStringable, utils::LifetimeTracker::Session>(&tracker);
expected_ops.emplace_back(1, utils::LifetimeOperationType::kValueConstruction);
auto p2 = p1;
ASSERT_TRUE(p1.has_value());
ASSERT_EQ(p1.invoke(), "Session 1");
ASSERT_FALSE(p1.reflect().sbo_enabled_);
ASSERT_FALSE(p1.reflect().SboEnabled);
ASSERT_TRUE(p2.has_value());
ASSERT_EQ(p2.invoke(), "Session 2");
ASSERT_FALSE(p2.reflect().sbo_enabled_);
ASSERT_FALSE(p2.reflect().SboEnabled);
expected_ops.emplace_back(2, utils::LifetimeOperationType::kCopyConstruction);
ASSERT_TRUE(tracker.GetOperations() == expected_ops);
}
Expand All @@ -190,13 +194,13 @@ TEST(ProxyCreationTests, TestMakeProxy_WithoutSBO_Lifetime_Move) {
utils::LifetimeTracker tracker;
std::vector<utils::LifetimeOperation> expected_ops;
{
auto p1 = pro::make_proxy<TestSmallFacade, utils::LifetimeTracker::Session>(&tracker);
auto p1 = pro::make_proxy<poly::TestSmallStringable, utils::LifetimeTracker::Session>(&tracker);
expected_ops.emplace_back(1, utils::LifetimeOperationType::kValueConstruction);
auto p2 = std::move(p1);
ASSERT_FALSE(p1.has_value());
ASSERT_TRUE(p2.has_value());
ASSERT_EQ(p2.invoke(), "Session 1");
ASSERT_FALSE(p2.reflect().sbo_enabled_);
ASSERT_FALSE(p2.reflect().SboEnabled);
ASSERT_TRUE(tracker.GetOperations() == expected_ops);
}
expected_ops.emplace_back(1, utils::LifetimeOperationType::kDestruction);
Expand Down
Loading

0 comments on commit 1937e4b

Please sign in to comment.