diff --git a/Manual.md b/Manual.md index a8408090..f9d14a30 100644 --- a/Manual.md +++ b/Manual.md @@ -1345,6 +1345,25 @@ luabridge::getGlobalNamespace (L) .endNamespace () ``` +Alternatively is possible to pass custom lambdas to construct the container, where the return value of those lambdas must be exactly the container specified: + +```cpp +class C : public std::enable_shared_from_this +{ + C () { } + C (int) { } +}; + +luabridge::getGlobalNamespace (L) + .beginNamespace ("test") + .beginClass ("C") + .addConstructorFrom> ( + []() { return std::make_shared (); }, + [](int value) { return std::make_shared (value); }) + .endClass () + .endNamespace () +``` + 3.5 - Mixing Lifetimes ---------------------- @@ -1774,7 +1793,11 @@ Class addConstructor (Functions... functions); /// Registers one or multiple overloaded constructors for type T when usable from intrusive container C. template -Class addConstructor (); +Class addConstructorFrom (); + +/// Registers one or multiple overloaded constructors for type T when usable from intrusive container C using callable arguments. +template +Class addConstructorFrom (Functions... functions); /// Registers allocator and deallocators for type T. template diff --git a/Source/LuaBridge/detail/CFunctions.h b/Source/LuaBridge/detail/CFunctions.h index 5633a133..fdbb07cb 100644 --- a/Source/LuaBridge/detail/CFunctions.h +++ b/Source/LuaBridge/detail/CFunctions.h @@ -1239,6 +1239,22 @@ struct placement_constructor } }; +//================================================================================================= +/** + * @brief Container allocator generators. + */ +template +struct container_constructor +{ + template + static C construct(const F& func, const Args& args) + { + auto alloc = [&func](auto&&... args) { return func(std::forward(args)...); }; + + return std::apply(alloc, args); + } +}; + //================================================================================================= /** * @brief External allocator generators. @@ -1362,5 +1378,35 @@ struct factory_forwarder Dealloc m_dealloc; }; +//================================================================================================= +/** + * @brief Container forwarder. + */ +template +struct container_forwarder +{ + explicit container_forwarder(F f) + : m_func(std::move(f)) + { + } + + C operator()(lua_State* L) + { + using FnTraits = function_traits; + using FnArgs = typename FnTraits::argument_types; + + auto obj = container_constructor::construct(m_func, make_arguments_list(L)); + + auto result = UserdataSharedHelper::push(L, obj); + if (! result) + raise_lua_error(L, "%s", result.message().c_str()); + + return obj; + } + +private: + F m_func; +}; + } // namespace detail } // namespace luabridge diff --git a/Source/LuaBridge/detail/Namespace.h b/Source/LuaBridge/detail/Namespace.h index 8ae207e4..0256a164 100644 --- a/Source/LuaBridge/detail/Namespace.h +++ b/Source/LuaBridge/detail/Namespace.h @@ -1103,6 +1103,70 @@ class Namespace : public detail::Registrar return *this; } + //========================================================================================= + /** + * @brief Add or replace a placement constructor. + * + * The primary placement constructor is invoked when calling the class type table like a function. + * + * The provider of the Function argument is responsible of doing placement new of the type T over the void* pointer provided to + * the method as first argument. + */ + template + auto addConstructor(Functions... functions) + -> std::enable_if_t<(detail::is_callable_v && ...) && (sizeof...(Functions) > 0), Class&> + { + static_assert(((detail::function_arity_excluding_v >= 1) && ...)); + static_assert(((std::is_same_v, void*>) && ...)); + + assertStackState(); // Stack: const table (co), class table (cl), static table (st) + + if constexpr (sizeof...(Functions) == 1) + { + ([&] + { + using F = detail::constructor_forwarder; + + lua_newuserdata_aligned(L, F(std::move(functions))); // Stack: co, cl, st, upvalue + lua_pushcclosure_x(L, &detail::invoke_proxy_constructor, 1); // Stack: co, cl, st, function + + } (), ...); + } + else + { + // create new closure of try_overloads with new table + lua_createtable(L, static_cast(sizeof...(Functions)), 0); // reserve space for N overloads + + int idx = 1; + + ([&] + { + using F = detail::constructor_forwarder; + + lua_createtable(L, 2, 0); // reserve space for: function, arity + lua_pushinteger(L, 1); + if constexpr (detail::is_any_cfunction_pointer_v) + lua_pushinteger(L, -1); + else + lua_pushinteger(L, static_cast(detail::function_arity_excluding_v) - 1); // 1: for void* ptr + lua_settable(L, -3); + lua_pushinteger(L, 2); + lua_newuserdata_aligned(L, F(std::move(functions))); + lua_pushcclosure_x(L, &detail::invoke_proxy_constructor, 1); + lua_settable(L, -3); + lua_rawseti(L, -2, idx); + ++idx; + + } (), ...); + + lua_pushcclosure_x(L, &detail::try_overload_functions, 1); + } + + rawsetfield(L, -2, "__call"); // Stack: co, cl, st + + return *this; + } + //========================================================================================= /** * @brief Add or replace a primary Constructor when the type is used from an intrusive container C. @@ -1152,19 +1216,15 @@ class Namespace : public detail::Registrar //========================================================================================= /** - * @brief Add or replace a placement constructor. - * - * The primary placement constructor is invoked when calling the class type table like a function. + * @brief Add or replace a primary Constructor when the type is used from an intrusive container C. * - * The provider of the Function argument is responsible of doing placement new of the type T over the void* pointer provided to - * the method as first argument. + * The provider of the Function argument is responsible of constructing the container C forwarding arguments in the callable Functions passed in. */ - template - auto addConstructor(Functions... functions) + template + auto addConstructorFrom(Functions... functions) -> std::enable_if_t<(detail::is_callable_v && ...) && (sizeof...(Functions) > 0), Class&> { - static_assert(((detail::function_arity_excluding_v >= 1) && ...)); - static_assert(((std::is_same_v, void*>) && ...)); + static_assert(((std::is_same_v, C>) && ...)); assertStackState(); // Stack: const table (co), class table (cl), static table (st) @@ -1172,7 +1232,7 @@ class Namespace : public detail::Registrar { ([&] { - using F = detail::constructor_forwarder; + using F = detail::container_forwarder; lua_newuserdata_aligned(L, F(std::move(functions))); // Stack: co, cl, st, upvalue lua_pushcclosure_x(L, &detail::invoke_proxy_constructor, 1); // Stack: co, cl, st, function @@ -1188,14 +1248,14 @@ class Namespace : public detail::Registrar ([&] { - using F = detail::constructor_forwarder; + using F = detail::container_forwarder; lua_createtable(L, 2, 0); // reserve space for: function, arity lua_pushinteger(L, 1); if constexpr (detail::is_any_cfunction_pointer_v) lua_pushinteger(L, -1); else - lua_pushinteger(L, static_cast(detail::function_arity_excluding_v) - 1); // 1: for void* ptr + lua_pushinteger(L, static_cast(detail::function_arity_excluding_v)); lua_settable(L, -3); lua_pushinteger(L, 2); lua_newuserdata_aligned(L, F(std::move(functions))); diff --git a/Tests/Source/Tests.cpp b/Tests/Source/Tests.cpp index 2d30ffb8..4bd8c9b6 100644 --- a/Tests/Source/Tests.cpp +++ b/Tests/Source/Tests.cpp @@ -615,6 +615,43 @@ TEST_F(LuaBridgeTest, StdSharedPtrSingle) EXPECT_EQ(2, a4->x); } +TEST_F(LuaBridgeTest, StdSharedPtrSingleCustomConstructor) +{ + luabridge::getGlobalNamespace(L) + .beginNamespace("test") + .beginClass("A1") + .addConstructorFrom>( + [] { return std::make_shared(); }) + .endClass() + .endNamespace(); + + luabridge::getGlobalNamespace(L) + .beginNamespace("test") + .beginClass("A2") + .addConstructorFrom>( + [] { return std::make_shared(); }, + [](int x) { return std::make_shared(x); }, + [](int x, int y) { return std::make_shared(x + y); }) + .endClass() + .endNamespace(); + + EXPECT_TRUE(runLua("result = test.A1()")); + auto a0 = result>(); + EXPECT_EQ(42, a0->x); + + EXPECT_TRUE(runLua("result = test.A2()")); + auto a1 = result>(); + EXPECT_EQ(42, a1->x); + + EXPECT_TRUE(runLua("result = test.A2(1337)")); + auto a2 = result>(); + EXPECT_EQ(1337, a2->x); + + EXPECT_TRUE(runLua("result = test.A2(11, 22)")); + auto a3 = result>(); + EXPECT_EQ(33, a3->x); +} + TEST_F(LuaBridgeTest, StdSharedPtrMultiple) { luabridge::getGlobalNamespace(L)