Git Product home page Git Product logo

proxy's Introduction

Proxy: Next Generation Polymorphism in C++

Proxy-CI

Do you want to facilitate lifetime management and maintenance of polymorphic objects in C++?

Do you want to be able to write polymorphic code in C++ as easily as in languages with GC (like Java or C#), while still having excellent runtime performance?

Have you tried other polymorphic programming libraries in C++ but found them deficient?

If so, this library is for you. 😉

For decades, object-based virtual table has been a de facto implementation of runtime polymorphism in many (compiled) programming languages. There are many drawbacks in this mechanism, including life management (because each object may have different size and ownership) and reflection (because it is hard to balance between usability and memory allocation). To workaround these drawbacks, some languages like Java or C# choose to sacrifice performance by introducing GC to facilitate lifetime management, and JIT-compile the source code at runtime to generate full metadata. We improved the theory and implemented as a C++ library without sacrificing performance, proposed to merge into the C++ standard.

The "proxy" is a single-header, cross-platform C++ library that Microsoft uses to make runtime polymorphism easier to implement and faster. Please find the design details at https://wg21.link/p3086.

Quick start

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), you are all set. You can also install the library via vcpkg, which is a C++ library manager invented by Microsoft, by searching for "proxy" (see vcpkg.info).

The majority of the library is defined in namespace pro. Some macros are provided (currently not included in the proposal for standardization) to simplify the definition of proxy prior to C++26. Here is a demo showing how to use this library to implement runtime polymorphism in a different way from the traditional inheritance-based approach:

// Specifications of abstraction
namespace spec {

PRO_DEF_MEMBER_DISPATCH(Draw, void(std::ostream& out));
PRO_DEF_MEMBER_DISPATCH(Area, double() noexcept);
PRO_DEF_FACADE(Drawable, PRO_MAKE_DISPATCH_PACK(Draw, Area));

}  // namespace spec

// Implementation
class Rectangle {
 public:
  void Draw(std::ostream& out) const
      { out << "{Rectangle: width = " << width_ << ", height = " << height_ << "}"; }
  void SetWidth(double width) { width_ = width; }
  void SetHeight(double height) { height_ = height; }
  double Area() const noexcept { return width_ * height_; }

 private:
  double width_;
  double height_;
};

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

// Client - Producer
pro::proxy<spec::Drawable> CreateRectangleAsDrawable(int width, int height) {
  Rectangle rect;
  rect.SetWidth(width);
  rect.SetHeight(height);
  return pro::make_proxy<spec::Drawable>(rect);
}

Here is another demo showing how to define overloads in a dispatch. Note that .invoke<> can be ommitted when only 1 dispatch is defined in a facade:

// Specifications of abstraction
namespace spec {

PRO_DEF_MEMBER_DISPATCH(Log, void(const char*), void(const char*, const std::exception&));
PRO_DEF_FACADE(Logger, Log);

}  // namespace spec

// Client - Consumer
void MyVerboseFunction(pro::proxy<spec::Logger> logger) {
  logger("hello");
  try {
    throw std::runtime_error{"runtime error!"};
  } catch (const std::exception& e) {
    logger("world", e);
  }
}

// Implementation
struct MyLogger {
  void Log(const char* s) {
    printf("[INFO] %s\n", s);
  }
  void Log(const char* s, const std::exception& e) {
    printf("[ERROR] %s (exception info: %s)\n", s, e.what());
  }
};

// Client - Producer
int main() {
  MyLogger logger;
  MyVerboseFunction(&logger);
  return 0;
}

By design, the body of a dispatch could be any code. While member function is one useful pattern supported by macro PRO_DEF_MEMBER_DISPATCH, free function is also supported with another macro PRO_DEF_FREE_DISPATCH. The following example uses PRO_DEF_FREE_DISPATCH and std::invoke to implement similar function wrapper as std::function and std::move_only_function and supports multiple overloads.

// Specifications of abstraction
namespace spec {

template <class... Overloads>
PRO_DEF_FREE_DISPATCH(Call, std::invoke, Overloads...);
template <class... Overloads>
PRO_DEF_FACADE(MovableCallable, Call<Overloads...>);
template <class... Overloads>
PRO_DEF_FACADE(CopyableCallable, Call<Overloads...>, pro::copyable_ptr_constraints);

}  // namespace spec

// MyFunction has similar functionality as std::function but supports multiple overloads
// MyMoveOnlyFunction has similar functionality as std::move_only_function but supports multiple overloads
template <class... Overloads>
using MyFunction = pro::proxy<spec::CopyableCallable<Overloads...>>;
template <class... Overloads>
using MyMoveOnlyFunction = pro::proxy<spec::MovableCallable<Overloads...>>;

int main() {
  auto f = [](auto&&... v) {
    printf("f() called. Args: ");
    ((std::cout << v << ":" << typeid(decltype(v)).name() << ", "), ...);
    puts("");
  };
  MyFunction<void(int)> p0{&f};
  p0(123);  // Prints "f() called. Args: 123:i," (assuming GCC)
  MyMoveOnlyFunction<void(), void(int), void(double)> p1{&f};
  p1();  // Prints "f() called. Args:"
  p1(456);  // Prints "f() called. Args: 456:i,"
  p1(1.2);  // Prints "f() called. Args: 1.2:d,"
  return 0;
}

Please find more details and discussions in the spec. The complete version of the "drawable" demo could be found in tests/proxy_integration_tests.cpp (also available on Compiler Explorer).

Minimum requirements for compilers

Family Minimum version Required flags
clang 15.0.0 -std=c++20
gcc 11.2 -std=c++20
MSVC 19.30 /std:c++20

Use proxy with CMake and Vcpkg

See more details in samples

  1. Set up vcpkg manifest
{
  "name": "<project_name>",
  "version": "0.1.0",
  "dependencies": [
    {
      "name": "proxy"
    }
  ]
}
  1. Integrate proxy in CMakeLists.txt
find_package(proxy CONFIG REQUIRED)
target_link_libraries(<target_name> PRIVATE msft_proxy)
  1. Run CMake with vcpkg toolchain file
cmake <source_dir> -B <build_dir> -DCMAKE_TOOLCHAIN_FILE=<vcpkg_dir>/scripts/buildsystems/vcpkg.cmake

Build and run tests with CMake

git clone https://github.com/microsoft/proxy.git
cd proxy
cmake -S . -B build
cmake --build ./build -j8
cd ./build
ctest -j8

Contributing

This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.

When you submit a pull request, a CLA bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA.

This project has adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact [email protected] with any additional questions or comments.

Trademarks

This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft trademarks or logos is subject to and must follow Microsoft's Trademark & Brand Guidelines. Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. Any use of third-party trademarks or logos are subject to those third-party's policies.

proxy's People

Contributors

coyorkdow avatar frederick-vs-ja avatar microsoft-github-operations[bot] avatar microsoftopensource avatar mingxwa avatar tian-lt avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

proxy's Issues

Fail to build with GCC 11.2

Compiling the following code with GCC 11.2 will cause internal compiler error, which is a regression since version 2.3.0.

#include <proxy/proxy.h>

PRO_DEF_FACADE(Any);

Here is the output from the compiler:

In file included from b.cc:1:
../proxy/proxy.h: In instantiation of ‘pro::details::is_tuple_like_well_formed<std::tuple<> >()::<lambda(std::index_sequence<I ...>)> [with long unsigned int ...I = {}; std::index_sequence<I ...> = std::integer_sequence<long unsigned int>]’:
../proxy/proxy.h:62:12:   required from ‘consteval bool pro::details::is_tuple_like_well_formed() [with T = std::tuple<>]’
../proxy/proxy.h:852:66:   required by substitution of ‘template<class O, class I>  requires (is_tuple_like_well_formed<I>)() struct pro::details::flat_reduction<O, I> [with O = std::tuple<>; I = std::tuple<>]’
../proxy/proxy.h:858:9:   required from ‘struct pro::details::facade_prototype<>’
b.cc:3:1:   required from here
../proxy/proxy.h:61:77: internal compiler error: in tsubst_pack_expansion, at cp/pt.c:13271
   61 |             return (requires { typename std::tuple_element_t<I, T>; } && ...);
      |                                                                             ^
0xe34ddb internal_error(char const*, ...)
        ???:0
0xe2b903 fancy_abort(char const*, int, char const*)
        ???:0
0x10e0992 tsubst_copy_and_build(tree_node*, tree_node*, int, tree_node*, bool, bool)
        ???:0
0x1174c07 tsubst_expr(tree_node*, tree_node*, int, tree_node*, bool)
        ???:0
0x1174db8 tsubst_expr(tree_node*, tree_node*, int, tree_node*, bool)
        ???:0
0x1174c65 tsubst_expr(tree_node*, tree_node*, int, tree_node*, bool)
        ???:0
0x1174c65 tsubst_expr(tree_node*, tree_node*, int, tree_node*, bool)
        ???:0
0x122e9ea instantiate_decl(tree_node*, bool, bool)
        ???:0
0x10822ff mark_used(tree_node*, int)
        ???:0
0x153cff2 build_op_call(tree_node*, vec<tree_node*, va_gc, vl_embed>**, int)
        ???:0
0x106ac0f finish_call_expr(tree_node*, vec<tree_node*, va_gc, vl_embed>**, bool, bool, int)
        ???:0
0x10e0e28 tsubst_copy_and_build(tree_node*, tree_node*, int, tree_node*, bool, bool)
        ???:0
0x1174c07 tsubst_expr(tree_node*, tree_node*, int, tree_node*, bool)
        ???:0
0x1174db8 tsubst_expr(tree_node*, tree_node*, int, tree_node*, bool)
        ???:0
0x1174cf3 tsubst_expr(tree_node*, tree_node*, int, tree_node*, bool)
        ???:0
0x1174c65 tsubst_expr(tree_node*, tree_node*, int, tree_node*, bool)
        ???:0
0x11750ce tsubst_expr(tree_node*, tree_node*, int, tree_node*, bool)
        ???:0
0x1174c65 tsubst_expr(tree_node*, tree_node*, int, tree_node*, bool)
        ???:0
0x11750ce tsubst_expr(tree_node*, tree_node*, int, tree_node*, bool)
        ???:0
0x1174cce tsubst_expr(tree_node*, tree_node*, int, tree_node*, bool)
        ???:0
Please submit a full bug report,
with preprocessed source if appropriate.
Please include the complete backtrace with any bug report.
See <file:///usr/share/doc/gcc-11/README.Bugs> for instructions.

[FR] operator bool

wrapper around has_value

ps. don't know if you get notifications for new comments in closed issues: #61

Differentiate interface abstraction from const member function

The member functions have same name, same parameters, and same return type, but with or without const qualifier, are regarded as two different functions. A class can has both two functions while one is virtual and another isn't. In proxy's design, an interface is identified by the template paramters of the base class pro::dispatch and the struct inherited the base. We cannot determine const or non-const through the interface definition.

I noticed that all the self operands are defined with the const qualified in both document and sample/unittests. In this way we can restrict the interface must be a const member function. If remove the const, then it's equivalent to define the interfaces for both const and non-const member functions. What we cannot do is define the interface to the non-const function only.

There is a possilble approach that differentiate between const and non-const by defining the operator() as const or non-const.

Lack support of overload resolution among multiple existing abstractions

Consider the following C++ code without using proxy:

struct Base1 {
  virtual void f() = 0;
  virtual ~Base1() = default;
};
struct Base2 {
  virtual void f(int v) = 0;
  virtual ~Base2() = default;
};
struct CombinedBase : Base1, Base2 {};

While both Base1::f() and Base2::f(int) can be called with a single symbol f on a value of type CombinedBase, there is no equivalent facility in the proxy library.

Compile error when `-ffreestanding` is enabled on GCC

Here is the output from my local dev machine when -ffreestanding is enabled on GCC.

In file included from \\work\1.cc:1:
\\work\proxy.h: In function 'pro::proxy<F> pro::details::make_proxy_impl(Args&& ...)':
\\work\proxy.h:808:12: error: 'allocator' is not a member of 'std'
  808 |       std::allocator<T>{}, std::forward<Args>(args)...);
      |            ^~~~~~~~~
\\work\proxy.h:15:1: note: 'std::allocator' is defined in header '<memory>'; did you forget to '#include <memory>'?
   14 | #include <utility>
  +++ |+#include <memory>
   15 | 
\\work\proxy.h:808:23: error: expected primary-expression before '>' token
  808 |       std::allocator<T>{}, std::forward<Args>(args)...);
      |                       ^
\\work\proxy.h:808:24: error: expected primary-expression before '{' token
  808 |       std::allocator<T>{}, std::forward<Args>(args)...);
      |                        ^

[FR] noexcept prototypes/signatures

i.e. please support noecept signatures, e.g.:

struct Foo : pro::details::dispatch_prototype<void( int ) noexcept> {
    decltype( auto ) operator()( auto && self, int const x ) { return self.foo( x ); }
};

at least it does not compile for me with the current master (Clang 17 and latest MSVC)

ps. likewise - a 'bit' harder :D - support custom compiler attributes (like [[gnu::pure]])

pps. If you could also give examples/documentation on how to use the library w/o the macros and detail namespace helpers?

MacOS g++-12 compile default linker error and mold linker with bus error

When I was trying to compile the project in MacOS 13 with default linker, it outcomes

image

When I switched linker to mold, the error becomes runtime:

image

And I test the mvp with

#include <iostream>
#include <map>
#include <string>
#include <cassert>
#include <vector>
#include <proxy/proxy.h>


enum class LifetimeOperationType {
  kNone,
  kValueConstruction,
  kInitializerListConstruction,
  kCopyConstruction,
  kMoveConstruction,
  kDestruction
};

struct LifetimeOperation {
  LifetimeOperation(int id, LifetimeOperationType type) : id_(id), type_(type) {}

  bool operator==(const LifetimeOperation& rhs) const
      { return id_ == rhs.id_ && type_ == rhs.type_; }

  int id_;
  LifetimeOperationType type_;
};

struct ConstructionFailure : std::exception {
  ConstructionFailure(LifetimeOperationType type) : type_(type) {}

  LifetimeOperationType type_;
};

class LifetimeTracker {
 public:
  LifetimeTracker() = default;
  LifetimeTracker(const LifetimeTracker&) = delete;

  class Session {
   public:
    Session(LifetimeTracker* host)
        : id_(host->AllocateId(LifetimeOperationType::kValueConstruction)),
          host_(host) {}
    Session(std::initializer_list<int>, LifetimeTracker* host)
        : id_(host->AllocateId(LifetimeOperationType::kInitializerListConstruction)),
          host_(host) {}
    Session(const Session& rhs)
        : id_(rhs.host_->AllocateId(LifetimeOperationType::kCopyConstruction)),
          host_(rhs.host_) {}
    Session(Session&& rhs) noexcept :
          id_(rhs.host_->AllocateId(LifetimeOperationType::kMoveConstruction)),
          host_(rhs.host_) {}
    ~Session() { host_->ops_.emplace_back(id_, LifetimeOperationType::kDestruction); }
    Session& operator*() { return *this; }
    friend std::string to_string(const Session& self) { return "Session " + std::to_string(self.id_); }

   private:
    int id_;
    LifetimeTracker* const host_;
  };

  const std::vector<LifetimeOperation>& GetOperations() const { return ops_; }
  void ThrowOnNextConstruction() { throw_on_next_construction_ = true; }

 private:
  int AllocateId(LifetimeOperationType operation_type) {
    if (throw_on_next_construction_) {
      throw_on_next_construction_ = false;
      throw ConstructionFailure{ operation_type };
    }
    ops_.emplace_back(++max_id_, operation_type);
    return max_id_;
  }

  int max_id_ = 0;
  bool throw_on_next_construction_ = false;
  std::vector<LifetimeOperation> ops_;
};

struct ToString : pro::dispatch<std::string()> {
  template <class T>
  std::string operator()(const T& self) {
    using std::to_string;
    return to_string(self);
  }
};


struct TestFacade : pro::facade<ToString> {
  static constexpr auto minimum_copyability = pro::constraint_level::nontrivial;
};

int main() {
  LifetimeTracker tracker;
  std::vector<LifetimeOperation> expected_ops;
  {
    tracker.ThrowOnNextConstruction();
    bool exception_thrown = false;
    try {
      pro::proxy<TestFacade> p{ std::in_place_type<LifetimeTracker::Session>, &tracker };
    } catch (const ConstructionFailure& e) {
      exception_thrown = true;
      assert(e.type_ ==LifetimeOperationType::kValueConstruction);
    }
    assert(exception_thrown==true);
    assert(tracker.GetOperations() == expected_ops);
  }
  return 0;
}

The TestFacade outcomes the std::__is_constant_evaluated segfault. How to resolve this issue in MacOS g++?

const version of proxy::invoke()

I was experimenting with this library using the sample code in the recent C++ Team Blog. I wanted to make multiple methods that operated on the same proxy so I did something like the following:

DoSomething1(const pro::proxy<Facade> &p) {
    p.invoke<dispatch1>();
}

But this won't compile because invoke() is non-const. Casting away the const works of course. Making the parameter non-const is not desirable because of the problem passing in temporary values.

[FR] SBO-only targets

Add a trait to specify that only targets that fit into the SBO are allowed (to be assigned).
(or is specifying noexcept copyiablity enough, i.e. it implies that?)

Storing pro::proxy<T> in container and iteration

Hi there,

I really like the look of this library, it seems like a very interesting idea.

One thing I might be missing is how to mimic the classic std::vector<Base*> polymorphic iteration where you might have something like...

std::vector<IDrawable>* drawables;

// ... add different types of derived drawables...

for (auto* drawable : drawables) {
    drawable->Draw(...);
}

(example types taken from https://devblogs.microsoft.com/cppblog/proxy-runtime-polymorphism-made-easier-than-ever/)

When I try this with the library it compiles...

std::vector<pro::proxy<DrawableFacade>> drawables;

Rectangle rect(100, 200);
Circle circle(50);

drawables.push_back(&rect);
drawables.push_back(&circle);

But when I try to do this...

for (auto drawable : drawables) {
    PrintDrawableToString(drawable);
}

I get the compile error...

error: call to deleted constructor of 'pro::proxy<DrawableFacade>'

Is there a means to do this using proxy or would you recommend a different approach? (I know the example is pretty contrived and usually these days you don't want to do that anyway because of the pointer indirection and likely poor cache locality but I was just curious if you could write something similar using proxy).

Thanks very much for your time!

[Question] Returning a proxy from dispatch

Would this be a valid implementation or is there some reason I would not want to do this?

    struct Draw : pro::dispatch<void(std::ostream&)> {
        template <class T>
        void operator()(const T& self, std::ostream& out) { self.Draw(out); }
    };
    
    struct Area : pro::dispatch<double()> {
        template <class T>
        double operator()(const T& self) { return self.Area(); }
    };

    struct Self;
    struct AreaFacade : pro::facade<Area> {};
    struct DrawFacade : pro::facade<Draw> {};
    struct DrawableFacade : pro::facade<Self, Area, Draw> {};
    
    struct Self : pro::dispatch<pro::proxy<AreaFacade>()> {
        template <class T>
        pro::proxy<AreaFacade> operator()(T& self) { return pro::make_proxy<AreaFacade>(std::move(self)); }
    };

Notice I'm converting a DrawableFacade to an AreaFacade in the self dispatch type. This would be an attempt to do something like a classic upcast I suppose where I have an implementation that needs only a subset of the current facade's available interface.

how about memory usage when compare to default virtual-table implementation ?

For example,
class base
{
public:
virtual void methodA(){}
virtual void methodB(){}
virtual void methodC(){}
}

and have 3 derived class derivedA, derivedB, derivedC.

In virtual-table method, when to store N objects, we can store them in only one vector vector<base*>;
while in proxy method, does it meas we need store then in thress vector as vector<proxy_methodA>, vector<proxy_methodB>, and vector<proxy_methodC> ?
If so, proxy method takes more memory then virtual-table method.

destructor call twice

vs2022

#include <iostream>
#include <sstream>
#include <proxy/proxy.h>

// Abstraction
struct Draw : pro::dispatch<void(std::ostream&)> {
    template <class T>
    void operator()(const T& self, std::ostream& out) { self.Draw(out); }
};
struct Area : pro::dispatch<double()> {
    template <class T>
    double operator()(const T& self) { return self.Area(); }
};
struct DrawableFacade : pro::facade<Draw, Area> {};

// Implementation
class Rectangle {
public:
    ~Rectangle() {
        std::cout << "~Rectangle()" << std::endl;
    }
    Rectangle() {
        std::cout << "Rectangle()" << std::endl;
    }
    void Draw(std::ostream& out) const {
        out << "{Rectangle: width = " << width_ << ", height = " << height_ << "}";
    }
    void SetWidth(double width) { width_ = width; }
    void SetHeight(double height) { height_ = height; }
    double Area() const { return width_ * height_; }

private:
    double width_;
    double height_;
};

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

// Client - Producer
pro::proxy<DrawableFacade> CreateRectangleAsDrawable(int width, int height) {
    Rectangle rect;
    rect.SetWidth(width);
    rect.SetHeight(height);
    return pro::make_proxy<DrawableFacade>(rect);
}

int main()
{
    auto p = CreateRectangleAsDrawable(10, 10);
    
    std::cout << PrintDrawableToString(p) << std::endl;
}

output

Rectangle()
~Rectangle()
shape = {Rectangle: width = 10, height = 10}, area = 100
~Rectangle()

Compilation error on MSVC 19.36

I'm in the process of adding proxy to Conan Center Index (conan-io/conan-center-index#22362) and a test build of proxy v2.1.0 against a copy of the main.cpp example provided in the repo fails on MSVC 19.36.32532.0:

proxy/proxy.h(622,45): error C3546: '...': there are no parameter packs available to expand
proxy/proxy.h(624,2): message : see reference to class template instantiation 'pro::details::combined_dispatch_prototype<Ds...>' being compiled

Full log:

======== Input profiles ========
Profile host:
[settings]
arch=x86_64
build_type=Release
compiler=msvc
compiler.cppstd=20
compiler.runtime=dynamic
compiler.runtime_type=Release
compiler.version=193
os=Windows

======== Testing the package: Building ========
proxy/2.1.0 (test package): RUN: cmake -G "Visual Studio 17 2022" -DCMAKE_TOOLCHAIN_FILE="C:/J2/w/prod-v2/bsr/cci-b6f0142c/recipes/proxy/all/test_package/build/msvc-193-x86_64-20-release/generators/conan_toolchain.cmake" -DCMAKE_INSTALL_PREFIX="C:/J2/w/prod-v2/bsr/cci-b6f0142c/recipes/proxy/all/test_package" -DCMAKE_POLICY_DEFAULT_CMP0091="NEW" "C:\J2\w\prod-v2\bsr\cci-b6f0142c\recipes\proxy\all\test_package"
-- Using Conan toolchain: C:/J2/w/prod-v2/bsr/cci-b6f0142c/recipes/proxy/all/test_package/build/msvc-193-x86_64-20-release/generators/conan_toolchain.cmake
-- Conan toolchain: C++ Standard 20 with extensions OFF
-- The CXX compiler identification is MSVC 19.36.32532.0
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.36.32532/bin/Hostx64/x64/cl.exe - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Conan: Target declared 'msft_proxy'
-- Configuring done
-- Generating done
-- Build files have been written to: C:/J2/w/prod-v2/bsr/cci-b6f0142c/recipes/proxy/all/test_package/build/msvc-193-x86_64-20-release

proxy/2.1.0 (test package): Running CMake.build()
proxy/2.1.0 (test package): RUN: cmake --build "C:\J2\w\prod-v2\bsr\cci-b6f0142c\recipes\proxy\all\test_package\build\msvc-193-x86_64-20-release" --config Release
MSBuild version 17.6.3+07e294721 for .NET Framework

  Checking Build System
  Building Custom Rule C:/J2/w/prod-v2/bsr/cci-b6f0142c/recipes/proxy/all/test_package/CMakeLists.txt
  test_package.cpp
C:\J2\w\prod-v2\bsr\75336\ccfff\p\proxy5414b57492048\p\include\proxy/proxy.h(622,45): error C3546: '...': there are no parameter packs available to expand [C:\J2\w\prod-v2\bsr\cci-b6f0142c\recipes\proxy\all\test_package\build\msvc-193-x86_64-20-release\test_package.vcxproj]
C:\J2\w\prod-v2\bsr\75336\ccfff\p\proxy5414b57492048\p\include\proxy/proxy.h(624,2): message : see reference to class template instantiation 'pro::details::combined_dispatch_prototype<Ds...>' being compiled [C:\J2\w\prod-v2\bsr\cci-b6f0142c\recipes\proxy\all\test_package\build\msvc-193-x86_64-20-release\test_package.vcxproj]

GCC, Clang and AppleClang built without issues in the CI.

Provide opt-in cv-ref qualifiers forwarding for proxy::invoke

At present, proxy::invoke is always qualifier free, which makes cv-proxy useless.
The operator() of std::function and std::function_ref is always const and they use qualifiers from underlying object. The operator() of std::move_only_function forwards cv-ref to call underlying object. Existing implementation indicates that forwarding of quals should be an opt-in mechanism.

Undefined behavior: Field `ptr_` of `proxy` is accessed with potentially wrong compile-time assumption

As per clause 8 of [basic.life] in the standard:

If, after the lifetime of an object has ended and before the storage which the object occupied is reused or
released, a new object is created at the storage location which the original object occupied, a pointer that
pointed to the original object, a reference that referred to the original object, or the name of the original
object will automatically refer to the new object and, once the lifetime of the new object has started, can be
used to manipulate the new object, if the original object is transparently replaceable (see below) by the new
object. An object o1 is transparently replaceable by an object o2 if:
— the storage that o2 occupies exactly overlays the storage that o1 occupied, and
o1 and o2 are of the same type (ignoring the top-level cv-qualifiers), and
— o1 is not a const, complete object, and
— neither o1 nor o2 is a potentially-overlapping subobject (6.7.2), and
— either o1 and o2 are both complete objects, or o1 and o2 are direct subobjects of objects p1 and p2,
respectively, and p1 is transparently replaceable by p2.
[Note 5 : If these conditions are not met, a pointer to the new object can be obtained from a pointer that represents
the address of its storage by calling std::launder (17.6.5). —end note]

There are many places in the implementation of proxy that reinterpret_cast the data field alignas(F::constraints.max_align) char ptr_[F::constraints.max_size] to a user-defined pointer type, which leads to undefined behavior, since char and a pointer type P may not be transparently replaceable as per the wording above.

Question: reducing boilerplate? Avoiding multiple pro::dispatch?

Hello, and thank you very much for your work on this library and your work on submitting it for standardization! I don't know what's the status on that, but I hope it does get added to a future version. :)

I would like to know if it's possible to reduce a bit the boilerplate for when one wants to state a certain "interface", in which a few members need to be provided, not just one. I was trying the following, for example (and I know the example is pretty silly):

struct SelfDebug : pro::dispatch<void()>,
                   pro::dispatch<void(int)>
{
    template <class T>
    void operator()(const T& self) { qDebug() << self; }
    template <class T>
    void operator()(const T& self, int width) { qDebug() << qSetFieldWidth(width) << self; }
};

struct Debuggable : pro::facade<SelfDebug> {};

This compiles, but then it fails to compile when trying to instantiate a proxy of Debuggable. I would have hoped that it's possible to workaround a single operator(), but I wasn't able to. Other libraries like Folly::Poly's example, have the ugliness and maybe error prone poly_call with a number, but allow a clearer overview of the interface by having it all grouped together more locally.

Would it be possible have something similar with proxy?

Thank you very much!

Test targets compile failed for the [-Werror=uninitialized] compile option for non MSVC compiler(gcc11.2)

The default tag BUILD_TESTING is set as ON for the project, and there is an target_compile_options(msft_proxy_tests PRIVATE -Wall -Wextra -Wpedantic -Werror) in CMakeLists.txt of test for non MSVC compiler, so when I compile the code in gcc11.2, the test cases will fail for:

error: ‘p.pro::proxy<{anonymous}::TestFacade>::meta_’ is used uninitialized [-Werror=uninitialized]
[build] 323 | if (rhs.meta_ != nullptr) {
[build] | ~~~~^~~~~
proxy_lifetime_tests.cpp: In member function ‘virtual void ProxyLifetimeTests_TestMoveAssignment_FromNull_ToSelf_Test::TestBody()’:
proxy_lifetime_tests.cpp:692:26: note: ‘p’ declared here
[build] 692 | pro::proxy p;

and a lot of Werror ...

Have a syntax to declare default implementation of a dispatch easier and avoid generating duplicated code

Inspired by @lyf6lyf, when a default implementation is needed for a dispatch (e.g., throw an exception or fallback to some other default logic when a type does not have required semantics), there is no direct syntax to author a dispatch. Currently there are two ways to implement requirements like this.

First way uses PRO_DEF_FREE_DISPATCH:

namespace details {
template <class T>
std::optional<std::string> GetFillColor(T& self) {
  if constexpr (requires { { self.GetFillColor() } -> std::convertible_to<std::string>; }) {
    return self.GetFillColor();
  } else {
    return std::nullopt;
  }
}
}  // namespace details
PRO_DEF_FREE_DISPATCH(GetFillColor, details::GetFillColor, std::optional<std::string>());

Second way uses PRO_DEF_MEMBER_DISPATCH and PRO_DEF_COMBINED_DISPATCH:

namespace details {
PRO_DEF_MEMBER_DISPATCH(GetFillColor, std::optional<std::string>());
constexpr auto GetFillColorFallback = [](auto& self) { return std::nullopt; };
}  // namespace details
PRO_DEF_COMBINED_DISPATCH(GetFillColor, details::GetFillColor, decltype(details::GetFillColorFallback));

Both ways require combination of multiple C++ techniques and do not generate optimal code for the default implementation. Specifically, when there are two or more types that goes into the fallback logic, the current implementation of proxy will encourage code generation of the virtual table to duplicate the fallback code, and this duplication is hard to be optimized by a compiler across multiple translation units. proxy needs to be aware of any fallback logic of a dispatch to generate optimal code. Therefore, the motivation of this enhacement comes from both usability and performance.

Missing API to interact with the underlying pointer types

Sometimes it is useful to invoke a dispatch on the underlying pointer types without dereferencing them. For example, when refactoring the following code with proxy:

struct IMyAbstraction { ...; };

std::shared_ptr<IMyAbstraction> p = ...;
std::weak_ptr<IMyAbstraction> weak{p};
...
auto locked = weak.lock();
if (static_cast<bool>(locked)) {
  ...
}

It is currently infeasible to create a std::weak_ptr from a proxy, even if the underlying pointer is std::shared_ptr, because there is currently no API to touch the underlying pointer via a proxy.

Time to revise the name of constraints

The names in the constraints are too verbose comparing to the existing facilities in the standard library. For example, it would be better to rename maximum_size as max_size given that "max" is a well-known adjective.

How to create a proxy object that is composed of another proxy object of the same type?

Is there a way to create a proxy object that is composed of another proxy object of the same type? This could be used to create a UI hierarchy for example.

The following example doesn't compile, but can compile once the drawable member variable of Rectangle is removed.

// Abstraction
struct Draw : pro::dispatch<void()> {
	template <class T>
	void operator()(const T& self) { self.draw(); }
};

struct DrawableFacade : pro::facade<Draw> {};

// Implementation
class Rectangle {
public:
	void draw() const {
		drawable.invoke();
	};

	pro::proxy<DrawableFacade> drawable;
};

// Client - Producer
pro::proxy<DrawableFacade> CreateRectangleAsDrawable(int width, int height) {
	Rectangle rect;
	return pro::make_proxy<DrawableFacade>(rect);
}

Here is the error output when compiled in VS2022 17.8.1:

.\proxy\proxy.h(273): error C2131: expression did not evaluate to a constant
  ...\proxy\proxy.h(273): note: failure was caused by a read of an uninitialized symbol
  ...\proxy\proxy.h(273): note: see usage of 'pro::proxiable<pro::details::deep_ptr<Rectangle>,DrawableFacade>'
  ...\proxy\proxy.h(273): note: the template instantiation context (the oldest one first) is
  ...\layout.cpp(61): note: see reference to function template instantiation 'pro::proxy<DrawableFacade> pro::make_proxy<DrawableFacade,Rectangle&>(T)' being compiled
          with
          [
              T=Rectangle &
          ]
  ...\proxy\proxy.h(521): note: see reference to function template instantiation 'pro::proxy<DrawableFacade> pro::details::make_proxy_impl<DrawableFacade,Rectangle,Rectangle&>(Rectangle &)' being compiled
  ...\proxy\proxy.h(343): note: see reference to variable template 'const bool pro::proxy<DrawableFacade>::HasPolyConstructor<pro::details::deep_ptr<Rectangle>,Rectangle &>' being compiled
  ...\proxy\proxy.h(273): note: see reference to variable template 'bool proxiable<pro::details::deep_ptr<Rectangle>,DrawableFacade>' being compiled
  ...\proxy\proxy.h(260): note: see reference to variable template 'const bool pro::details::facade_traits_impl<DrawableFacade,std::tuple<Draw> >::applicable_pointer<pro::details::deep_ptr<Rectangle> >' being compiled
  ...\proxy\proxy.h(238): note: see reference to function template instantiation 'bool pro::details::has_copyability<pro::details::deep_ptr<Rectangle>>(pro::constraint_level)' being compiled
  ...\proxy\proxy.h(50): note: see reference to variable template 'const bool is_trivially_copy_constructible_v<pro::details::deep_ptr<Rectangle> >' being compiled
  ...\proxy\proxy.h(491): note: see reference to variable template 'const bool is_constructible_v<Rectangle,pro::details::deep_ptr<Rectangle> const &>' being compiled
  ...\proxy\proxy.h(338): note: see reference to variable template 'const bool pro::proxy<DrawableFacade>::HasPolyConstructor<pro::details::deep_ptr<Rectangle>,pro::details::deep_ptr<Rectangle> const &>' being compiled
C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.38.33130\include\xtr1common(69): error C2131: expression did not evaluate to a constant
  C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.38.33130\include\xtr1common(69): note: failure was caused by a read of an uninitialized symbol
  C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.38.33130\include\xtr1common(69): note: see usage of 'pro::proxiable<pro::details::deep_ptr<Rectangle>,DrawableFacade>'
  C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.38.33130\include\xtr1common(69): note: the template instantiation context (the oldest one first) is
  ...\proxy\proxy.h(272): note: see reference to alias template instantiation 'std::conditional_t<pro::proxiable<pro::details::deep_ptr<Rectangle>,DrawableFacade>,std::is_constructible<pro::details::deep_ptr<Rectangle>,const pro::details::deep_ptr<Rectangle> &>,std::integral_constant<bool,false>>' being compiled
...\proxy\proxy.h(506): error C2440: '<function-style-cast>': cannot convert from 'initializer list' to 'pro::proxy<DrawableFacade>'
  ...\proxy\proxy.h(506): note: 'pro::proxy<DrawableFacade>::proxy': function does not take 2 arguments
  ...\proxy\proxy.h(346): note: could be 'pro::proxy<DrawableFacade>::proxy(std::in_place_type_t<_Ty>,std::initializer_list<_Elem>,Args &&...) noexcept(<expr>)'
  ...\proxy\proxy.h(341): note: or       'pro::proxy<DrawableFacade>::proxy(std::in_place_type_t<_Ty>,Args &&...) noexcept(<expr>)'
  ...\proxy\proxy.h(337): note: or       'pro::proxy<DrawableFacade>::proxy(P &&) noexcept(<expr>)'
  ...\proxy\proxy.h(506): note: while trying to match the argument list '(const std::in_place_type_t<pro::details::deep_ptr<Rectangle>>, Rectangle)'

[FR] Add std::function::target() equivalent

...yet without a separate vtable call entry:

  • a void * returning version (the user has to know what is actually inside)
  • add a simple boolean to the vtable/meta object: specifying whether the assigned target is in the SBO (and so you return a pointer to the SBO) or on the heap (so you return the pointer stored at the begining of the SBO, I suppose)
    (and/or take into consideration #66)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.