Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add macros to simplify syntax of creating dispatches and facades #46

Merged
merged 3 commits into from
Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)...); \
mingxwa marked this conversation as resolved.
Show resolved Hide resolved
} \
}
#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