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

Added missing addConstructorFrom #127

Merged
merged 2 commits into from
May 3, 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
25 changes: 24 additions & 1 deletion Manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 () { }
C (int) { }
};

luabridge::getGlobalNamespace (L)
.beginNamespace ("test")
.beginClass <C> ("C")
.addConstructorFrom<std::shared_ptr<C>> (
[]() { return std::make_shared<C> (); },
[](int value) { return std::make_shared<C> (value); })
.endClass ()
.endNamespace ()
```

3.5 - Mixing Lifetimes
----------------------

Expand Down Expand Up @@ -1774,7 +1793,11 @@ Class<T> addConstructor (Functions... functions);

/// Registers one or multiple overloaded constructors for type T when usable from intrusive container C.
template <class C, class... Functions>
Class<T> addConstructor ();
Class<T> addConstructorFrom ();

/// Registers one or multiple overloaded constructors for type T when usable from intrusive container C using callable arguments.
template <class C, class... Functions>
Class<T> addConstructorFrom (Functions... functions);

/// Registers allocator and deallocators for type T.
template <class Alloc, class Dealloc>
Expand Down
46 changes: 46 additions & 0 deletions Source/LuaBridge/detail/CFunctions.h
Original file line number Diff line number Diff line change
Expand Up @@ -1239,6 +1239,22 @@ struct placement_constructor
}
};

//=================================================================================================
/**
* @brief Container allocator generators.
*/
template <class C>
struct container_constructor
{
template <class F, class Args>
static C construct(const F& func, const Args& args)
{
auto alloc = [&func](auto&&... args) { return func(std::forward<decltype(args)>(args)...); };

return std::apply(alloc, args);
}
};

//=================================================================================================
/**
* @brief External allocator generators.
Expand Down Expand Up @@ -1362,5 +1378,35 @@ struct factory_forwarder
Dealloc m_dealloc;
};

//=================================================================================================
/**
* @brief Container forwarder.
*/
template <class C, class F>
struct container_forwarder
{
explicit container_forwarder(F f)
: m_func(std::move(f))
{
}

C operator()(lua_State* L)
{
using FnTraits = function_traits<F>;
using FnArgs = typename FnTraits::argument_types;

auto obj = container_constructor<C>::construct(m_func, make_arguments_list<FnArgs, 2>(L));

auto result = UserdataSharedHelper<C, false>::push(L, obj);
if (! result)
raise_lua_error(L, "%s", result.message().c_str());

return obj;
}

private:
F m_func;
};

} // namespace detail
} // namespace luabridge
84 changes: 72 additions & 12 deletions Source/LuaBridge/detail/Namespace.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <class... Functions>
auto addConstructor(Functions... functions)
-> std::enable_if_t<(detail::is_callable_v<Functions> && ...) && (sizeof...(Functions) > 0), Class<T>&>
{
static_assert(((detail::function_arity_excluding_v<Functions, lua_State*> >= 1) && ...));
static_assert(((std::is_same_v<detail::function_argument_t<0, Functions>, void*>) && ...));

assertStackState(); // Stack: const table (co), class table (cl), static table (st)

if constexpr (sizeof...(Functions) == 1)
{
([&]
{
using F = detail::constructor_forwarder<T, Functions>;

lua_newuserdata_aligned<F>(L, F(std::move(functions))); // Stack: co, cl, st, upvalue
lua_pushcclosure_x(L, &detail::invoke_proxy_constructor<F>, 1); // Stack: co, cl, st, function

} (), ...);
}
else
{
// create new closure of try_overloads with new table
lua_createtable(L, static_cast<int>(sizeof...(Functions)), 0); // reserve space for N overloads

int idx = 1;

([&]
{
using F = detail::constructor_forwarder<T, Functions>;

lua_createtable(L, 2, 0); // reserve space for: function, arity
lua_pushinteger(L, 1);
if constexpr (detail::is_any_cfunction_pointer_v<Functions>)
lua_pushinteger(L, -1);
else
lua_pushinteger(L, static_cast<int>(detail::function_arity_excluding_v<Functions, lua_State*>) - 1); // 1: for void* ptr
lua_settable(L, -3);
lua_pushinteger(L, 2);
lua_newuserdata_aligned<F>(L, F(std::move(functions)));
lua_pushcclosure_x(L, &detail::invoke_proxy_constructor<F>, 1);
lua_settable(L, -3);
lua_rawseti(L, -2, idx);
++idx;

} (), ...);

lua_pushcclosure_x(L, &detail::try_overload_functions<true>, 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.
Expand Down Expand Up @@ -1152,27 +1216,23 @@ 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 <class... Functions>
auto addConstructor(Functions... functions)
template <class C, class... Functions>
auto addConstructorFrom(Functions... functions)
-> std::enable_if_t<(detail::is_callable_v<Functions> && ...) && (sizeof...(Functions) > 0), Class<T>&>
{
static_assert(((detail::function_arity_excluding_v<Functions, lua_State*> >= 1) && ...));
static_assert(((std::is_same_v<detail::function_argument_t<0, Functions>, void*>) && ...));
static_assert(((std::is_same_v<detail::function_result_t<Functions>, C>) && ...));

assertStackState(); // Stack: const table (co), class table (cl), static table (st)

if constexpr (sizeof...(Functions) == 1)
{
([&]
{
using F = detail::constructor_forwarder<T, Functions>;
using F = detail::container_forwarder<C, Functions>;

lua_newuserdata_aligned<F>(L, F(std::move(functions))); // Stack: co, cl, st, upvalue
lua_pushcclosure_x(L, &detail::invoke_proxy_constructor<F>, 1); // Stack: co, cl, st, function
Expand All @@ -1188,14 +1248,14 @@ class Namespace : public detail::Registrar

([&]
{
using F = detail::constructor_forwarder<T, Functions>;
using F = detail::container_forwarder<C, Functions>;

lua_createtable(L, 2, 0); // reserve space for: function, arity
lua_pushinteger(L, 1);
if constexpr (detail::is_any_cfunction_pointer_v<Functions>)
lua_pushinteger(L, -1);
else
lua_pushinteger(L, static_cast<int>(detail::function_arity_excluding_v<Functions, lua_State*>) - 1); // 1: for void* ptr
lua_pushinteger(L, static_cast<int>(detail::function_arity_excluding_v<Functions, lua_State*>));
lua_settable(L, -3);
lua_pushinteger(L, 2);
lua_newuserdata_aligned<F>(L, F(std::move(functions)));
Expand Down
37 changes: 37 additions & 0 deletions Tests/Source/Tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,43 @@ TEST_F(LuaBridgeTest, StdSharedPtrSingle)
EXPECT_EQ(2, a4->x);
}

TEST_F(LuaBridgeTest, StdSharedPtrSingleCustomConstructor)
{
luabridge::getGlobalNamespace(L)
.beginNamespace("test")
.beginClass<A>("A1")
.addConstructorFrom<std::shared_ptr<A>>(
[] { return std::make_shared<A>(); })
.endClass()
.endNamespace();

luabridge::getGlobalNamespace(L)
.beginNamespace("test")
.beginClass<A>("A2")
.addConstructorFrom<std::shared_ptr<A>>(
[] { return std::make_shared<A>(); },
[](int x) { return std::make_shared<A>(x); },
[](int x, int y) { return std::make_shared<A>(x + y); })
.endClass()
.endNamespace();

EXPECT_TRUE(runLua("result = test.A1()"));
auto a0 = result<std::shared_ptr<A>>();
EXPECT_EQ(42, a0->x);

EXPECT_TRUE(runLua("result = test.A2()"));
auto a1 = result<std::shared_ptr<A>>();
EXPECT_EQ(42, a1->x);

EXPECT_TRUE(runLua("result = test.A2(1337)"));
auto a2 = result<std::shared_ptr<A>>();
EXPECT_EQ(1337, a2->x);

EXPECT_TRUE(runLua("result = test.A2(11, 22)"));
auto a3 = result<std::shared_ptr<A>>();
EXPECT_EQ(33, a3->x);
}

TEST_F(LuaBridgeTest, StdSharedPtrMultiple)
{
luabridge::getGlobalNamespace(L)
Expand Down