From 2f705127ab1bdebf2573027ef443b7a490121587 Mon Sep 17 00:00:00 2001 From: Henry Kleynhans Date: Fri, 19 Jul 2019 12:47:22 +0100 Subject: [PATCH] `class_.constructor` to accept `std::function` and function object Enable `class_.constructor` to accept both `std::function` and function object types. This is useful in scenarios where you want a stateful function object for purposes such as gathering statistics or performing other generic functionality on each call. --- site/source/docs/api_reference/bind.h.rst | 25 +++- system/include/emscripten/bind.h | 135 +++++++++++++++------- tests/embind/embind.test.js | 13 +++ tests/embind/embind_test.cpp | 38 ++++++ 4 files changed, 162 insertions(+), 49 deletions(-) diff --git a/site/source/docs/api_reference/bind.h.rst b/site/source/docs/api_reference/bind.h.rst index 252959c76e8bb..8af38d1e629d0 100644 --- a/site/source/docs/api_reference/bind.h.rst +++ b/site/source/docs/api_reference/bind.h.rst @@ -533,17 +533,32 @@ Classes .. _embind-class-function-pointer-constructor: - .. cpp:function:: const class_& constructor(ReturnType (*factory)(Args...), Policies...) const + .. cpp:function:: const class_& constructor(Callable callable, Policies...) const .. code-block:: cpp //prototype - template - EMSCRIPTEN_ALWAYS_INLINE const class_& constructor(ReturnType (*factory)(Args...), Policies...) const + template + EMSCRIPTEN_ALWAYS_INLINE const class_& constructor(Callable callable, Policies...) const + + Class constructor for objects that use a factory function to create the object. This method will accept either a function pointer, ``std::function`` + object or function object which will return a newly constructed object. When the ``Callable`` is a function object the function signature must be + explicitly specified in the ``Signature`` template parameter in the format ``ReturnType (Args...)``. For ``Callable`` types other than function objects + the method signature will be deduced. - Class constructor for objects that use a factory function to create the object. See :ref:`embind-external-constructors` for more information. + The following are all valid calls to ``constructor``: - :param ReturnType (\*factory)(Args...): The address of the class factory function. + .. code-block:: cpp + + using namespace std::placeholders; + myClass1.constructor(&my_factory); + myClass2.constructor(std::function(&class2_factory)); + myClass3.constructor(std::bind(Class3Functor(), _1)); + + See :ref:`embind-external-constructors` for more information. + + + :param Callable callable Note that ``Callable`` may be either a member function pointer, function pointer, ``std::function`` or function object. :param Policies... policies: |policies-argument| :returns: |class_-function-returns| diff --git a/system/include/emscripten/bind.h b/system/include/emscripten/bind.h index 590eceb0d25f5..524a5e5b754c8 100644 --- a/system/include/emscripten/bind.h +++ b/system/include/emscripten/bind.h @@ -352,6 +352,31 @@ namespace emscripten { ); } }; + + template + struct FunctorInvoker { + static typename internal::BindingType::WireType invoke( + FunctorType& function, + typename internal::BindingType::WireType... args + ) { + return internal::BindingType::toWireType( + function( + internal::BindingType::fromWireType(args)...) + ); + } + }; + + template + struct FunctorInvoker { + static void invoke( + FunctorType& function, + typename internal::BindingType::WireType... args + ) { + function( + internal::BindingType::fromWireType(args)...); + } + }; + } //////////////////////////////////////////////////////////////////////////////// @@ -487,37 +512,6 @@ namespace emscripten { } }; - template - struct FunctorInvoker { - - static typename internal::BindingType::WireType invoke( - FunctorType& function, - typename internal::BindingType::WireType wireThis, - typename internal::BindingType::WireType... args - ) { - return internal::BindingType::toWireType( - function( - internal::BindingType::fromWireType(wireThis), - internal::BindingType::fromWireType(args)...) - ); - } - }; - - template - struct FunctorInvoker { - using FunctionType = std::function; - - static void invoke( - FunctorType& function, - typename internal::BindingType::WireType wireThis, - typename internal::BindingType::WireType... args - ) { - function( - internal::BindingType::fromWireType(wireThis), - internal::BindingType::fromWireType(args)...); - } - }; - template + struct RegisterClassConstructor; + + template + struct RegisterClassConstructor { + + template + static void invoke(ReturnType (*factory)(Args...)) { + typename WithPolicies::template ArgTypeList args; + auto invoke = &Invoker::invoke; + _embind_register_class_constructor( + TypeID::get(), + args.getCount(), + args.getTypes(), + getSignature(invoke), + reinterpret_cast(invoke), + reinterpret_cast(factory)); + } + }; + + template + struct RegisterClassConstructor> { + + template + static void invoke(std::function factory) { + typename WithPolicies::template ArgTypeList args; + auto invoke = &FunctorInvoker::invoke; + _embind_register_class_constructor( + TypeID::get(), + args.getCount(), + args.getTypes(), + getSignature(invoke), + reinterpret_cast(invoke), + reinterpret_cast(getContext(factory))); + } + }; + + template + struct RegisterClassConstructor { + + template + static void invoke(Callable& factory) { + typename WithPolicies::template ArgTypeList args; + auto invoke = &FunctorInvoker::invoke; + _embind_register_class_constructor( + TypeID::get(), + args.getCount(), + args.getTypes(), + getSignature(invoke), + reinterpret_cast(invoke), + reinterpret_cast(getContext(factory))); + } + }; + //////////////////////////////////////////////////////////////////////////// // RegisterClassMethod //////////////////////////////////////////////////////////////////////////// @@ -1328,20 +1380,15 @@ namespace emscripten { policies...); } - template - EMSCRIPTEN_ALWAYS_INLINE const class_& constructor(ReturnType (*factory)(Args...), Policies...) const { - using namespace internal; + template + EMSCRIPTEN_ALWAYS_INLINE const class_& constructor(Callable callable, Policies...) const { - // TODO: allows all raw pointers... policies need a rethink - typename WithPolicies::template ArgTypeList args; - auto invoke = &Invoker::invoke; - _embind_register_class_constructor( - TypeID::get(), - args.getCount(), - args.getTypes(), - getSignature(invoke), - reinterpret_cast(invoke), - reinterpret_cast(factory)); + using invoker = internal::RegisterClassConstructor< + typename std::conditional::value, + Callable, + Signature>::type>; + + invoker::template invoke(callable); return *this; } diff --git a/tests/embind/embind.test.js b/tests/embind/embind.test.js index 7154a31a0efa1..0e1163cd0e7c2 100644 --- a/tests/embind/embind.test.js +++ b/tests/embind/embind.test.js @@ -1192,6 +1192,19 @@ module({ b.delete(); }); + test("function objects as class constructors", function() { + let a = new cm.ConstructFromStdFunction("foo", 10); + assert.equal("foo", a.getVal()); + assert.equal(10, a.getA()); + + let b = new cm.ConstructFromFunctionObject("bar", 12); + assert.equal("bar", b.getVal()); + assert.equal(12, b.getA()); + + a.delete(); + b.delete(); + }); + test("function objects as class methods", function() { let b = cm.ValHolder.makeValHolder("foo"); diff --git a/tests/embind/embind_test.cpp b/tests/embind/embind_test.cpp index f5167dc8ffc9c..e3631d6ff8c21 100644 --- a/tests/embind/embind_test.cpp +++ b/tests/embind/embind_test.cpp @@ -1690,6 +1690,32 @@ unsigned long load_unsigned_long() { return ulong; } +template +class ConstructFromFunctor { +public: + ConstructFromFunctor(const val& v, int a) + : v_(v), a_(a) + {} + + val getVal() const { + return v_; + } + + int getA() const { + return a_; + } + +private: + val v_; + int a_; +}; + +template +ConstructFromFunctor construct_from_functor_mixin(const val& v, int i) +{ + return {v, i}; +} + EMSCRIPTEN_BINDINGS(tests) { register_vector("IntegerVector"); register_vector("CharVector"); @@ -1794,6 +1820,18 @@ EMSCRIPTEN_BINDINGS(tests) { using namespace std::placeholders; + class_>("ConstructFromStdFunction") + .constructor(std::function(const val&, int)>(&construct_from_functor_mixin<1>)) + .function("getVal", &ConstructFromFunctor<1>::getVal) + .function("getA", &ConstructFromFunctor<1>::getA) + ; + + class_>("ConstructFromFunctionObject") + .constructor(const val&, int)>(std::bind(&construct_from_functor_mixin<2>, _1, _2)) + .function("getVal", &ConstructFromFunctor<2>::getVal) + .function("getA", &ConstructFromFunctor<2>::getA) + ; + class_("ValHolder") .smart_ptr>("std::shared_ptr") .constructor()