From c7bfda506307244536dc4a51505873f9ddb7c386 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Mon, 4 May 2026 23:00:46 +0200 Subject: [PATCH 1/7] More justfile --- justfile | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/justfile b/justfile index 9a84bf48..c4f7ce6d 100644 --- a/justfile +++ b/justfile @@ -5,6 +5,15 @@ default: generate: cmake -G Xcode -B Build -DLUABRIDGE_BENCHMARKS=ON . +open: generate + -open Build/LuaBridge.xcodeproj + +build: generate + cmake --build Build --config Debug --target LuaBridgeTests53 -j8 + +test: build + ./Build/Tests/Debug/LuaBridgeTests53 + sanitize TYPE='address': cmake -G Xcode -B Build -DLUABRIDGE_SANITIZE={{TYPE}} . From 975ebb88bf50e7d269fcea5a30c9540f0f01a02e Mon Sep 17 00:00:00 2001 From: kunitoki Date: Tue, 5 May 2026 00:07:32 +0200 Subject: [PATCH 2/7] Add support for a workable bind_front --- Manual.md | 36 ++++++- Source/LuaBridge/detail/FuncTraits.h | 136 ++++++++++++++++++++++++++- Tests/Source/NamespaceTests.cpp | 46 +++++++++ 3 files changed, 215 insertions(+), 3 deletions(-) diff --git a/Manual.md b/Manual.md index e9f74774..945c5caf 100644 --- a/Manual.md +++ b/Manual.md @@ -32,7 +32,8 @@ Contents * [2.3.1 - Multiple Inheritance](#231---multiple-inheritance) * [2.4 - Property Member Proxies](#24---property-member-proxies) * [2.5 - Function Member Proxies](#25---function-member-proxies) - * [2.5.1 - Function Overloading](#251---function-overloading) + * [2.5.1 - Partial Application with luabridge::bind_front](#251---partial-application-with-luabridgebind_front) + * [2.5.2 - Function Overloading](#252---function-overloading) * [2.6 - Constructors](#26---constructors) * [2.6.1 - Constructor Proxies](#261---constructor-proxies) * [2.6.2 - Constructor Factories](#262---constructor-factories) @@ -602,7 +603,38 @@ luabridge::getGlobalNamespace (L) Of course when not capturing, it is better to prefix the lambda with `+` so it is converted and stored internally to a function pointer instead of an `std::function`, so it is actually lighter to store and faster to call. -### 2.5.1 - Function Overloading +### 2.5.1 - Partial Application with luabridge::bind_front + +`std::bind_front` (C++20) returns a callable whose `operator()` is a template, so LuaBridge cannot statically resolve its argument types. Passing such a result directly to `addFunction` therefore fails to compile. The workaround without `luabridge::bind_front` is an explicit cast to `std::function`: + +```cpp +int add (int a, int b) { return a + b; } + +// Verbose: explicit signature required +ns.addFunction ("add10", std::function (std::bind_front (&add, 10))); +``` + +`luabridge::bind_front` is a drop-in replacement that deduces the remaining argument types from the underlying callable's signature, so no explicit cast is needed: + +```cpp +// Concise: types are deduced automatically +ns.addFunction ("add10", luabridge::bind_front (&add, 10)); +``` + +It also works with member function pointers, where the bound object is not counted as a Lua-visible parameter: + +```cpp +struct Vec { float coord [3]; }; +float getCoord (const Vec* v, int i) { return v->coord [i]; } + +ns.beginClass ("Vec") + .addFunction ("x", luabridge::bind_front (&getCoord, 0)) + .addFunction ("y", luabridge::bind_front (&getCoord, 1)) + .addFunction ("z", luabridge::bind_front (&getCoord, 2)) +.endClass (); +``` + +### 2.5.2 - Function Overloading When specifying more than one method to the `addFunction` or `addStaticFunction` of both `Namespace` and `Class`, those overloads will be invoked in case of a call. Only overloads that have matched arguments arity will be considered, and they will be tried from first to last until the call succeeds. diff --git a/Source/LuaBridge/detail/FuncTraits.h b/Source/LuaBridge/detail/FuncTraits.h index c121bc48..916328cf 100644 --- a/Source/LuaBridge/detail/FuncTraits.h +++ b/Source/LuaBridge/detail/FuncTraits.h @@ -195,8 +195,32 @@ struct function_traits_impl : }; #endif +template +struct has_call_operator : std::false_type +{ +}; + +template +struct has_call_operator> : std::true_type +{ +}; + +template +inline static constexpr bool has_call_operator_v = has_call_operator::value; + +template +struct functor_traits_impl +{ +}; + +template +struct functor_traits_impl>> : function_traits_impl +{ +}; + template -struct functor_traits_impl : function_traits_impl +struct functor_traits_impl && std::is_invocable_v>> + : function_traits_base> { }; @@ -213,6 +237,19 @@ struct function_traits : std::conditional_t, { }; +template , class = void> +struct has_function_traits : std::false_type +{ +}; + +template +struct has_function_traits::result_type, typename function_traits::argument_types>> : std::true_type +{ +}; + +template +inline static constexpr bool has_function_traits_v = has_function_traits::value; + //================================================================================================= /** * @brief Deduces the argument type of a callble object or void in case it has no argument. @@ -303,6 +340,12 @@ struct is_callable> static constexpr bool value = true; }; +template +struct is_callable && !has_call_operator_v && has_function_traits_v>> +{ + static constexpr bool value = true; +}; + template struct is_callable && std::is_function_v>>> { @@ -547,5 +590,96 @@ struct remove_first_type> template using remove_first_type_t = typename remove_first_type::type; +//================================================================================================= +/** + * @brief Drop the first N types from a tuple. + */ +template +struct tuple_drop_first +{ + using type = typename tuple_drop_first>::type; +}; + +template +struct tuple_drop_first<0, Tuple> +{ + using type = Tuple; +}; + +template +using tuple_drop_first_t = typename tuple_drop_first::type; + +//================================================================================================= +/** + * @brief Internal storage for luabridge::bind_front — exposes a non-template operator() so that + * function_traits can statically resolve result_type and argument_types. + */ +template +struct bind_front_wrapper; + +template +struct bind_front_wrapper, Fn, BoundArgs...> +{ + Fn fn_; + std::tuple bound_; + + template + bind_front_wrapper(F&& f, BA&&... ba) + : fn_(std::forward(f)), bound_(std::forward(ba)...) + { + } + + R operator()(Remaining... args) const + { + return std::apply([&](const auto&... ba) { return std::invoke(fn_, ba..., args...); }, bound_); + } +}; + } // namespace detail + +//================================================================================================= +/** + * @brief Drop-in replacement for std::bind_front with statically introspectable argument types. + * + * std::bind_front returns an object whose operator() is a template, so its argument and result + * types cannot be resolved at compile time without an explicit std::function cast. This + * wrapper stores the callable and its leading bound arguments, then exposes a concrete + * non-template operator() whose parameter types are derived directly from the underlying + * callable's signature. This lets LuaBridge register the result with addFunction / addProperty / + * addMethod without any extra annotation. + * + * @code + * // Instead of: + * ns.addFunction("add", std::function(std::bind_front(&add2, 10))); + * + * // Write: + * ns.addFunction("add", luabridge::bind_front(&add2, 10)); + * @endcode + * + * For member function pointers the implicit object argument consumed by std::invoke is not + * counted as part of the remaining (Lua-visible) parameter list, matching std::bind_front + * semantics. + * + * @tparam F Callable type (function pointer, member function pointer, functor). + * @tparam BoundArgs Leading argument types to bind. + * @param f The callable to wrap. + * @param args Leading arguments forwarded into the wrapper by value. + * @return A callable object whose operator() accepts the remaining (unbound) arguments. + */ +template +auto bind_front(F&& f, BoundArgs&&... args) +{ + using Fn = std::decay_t; + using FnTraits = detail::function_traits; + + static constexpr std::size_t skip = FnTraits::is_member ? 1u : 0u; + static constexpr std::size_t num_effective_bound = sizeof...(BoundArgs) - skip; + + using remaining = detail::tuple_drop_first_t; + using R = typename FnTraits::result_type; + + return detail::bind_front_wrapper...>( + std::forward(f), std::forward(args)...); +} + } // namespace luabridge diff --git a/Tests/Source/NamespaceTests.cpp b/Tests/Source/NamespaceTests.cpp index 2cfd9b6c..412d06fe 100644 --- a/Tests/Source/NamespaceTests.cpp +++ b/Tests/Source/NamespaceTests.cpp @@ -588,6 +588,12 @@ T Function(T param) return param; } +template +T Function2(T param1, T param2) +{ + return param1 + param2; +} + int LuaFunction(lua_State* L) { lua_pushnumber(L, 42); @@ -622,6 +628,46 @@ TEST_F(NamespaceTests, StdFunctions) ASSERT_EQ(12, result()); } +TEST_F(NamespaceTests, StdBindFunctions) +{ + { + luabridge::getGlobalNamespace(L).addFunction("Function", std::bind(&Function, 1)); + + runLua("result = Function ()"); + ASSERT_TRUE(result().isNumber()); + ASSERT_EQ(1, result()); + } + + { + luabridge::getGlobalNamespace(L).addFunction("Function", + std::function(std::bind(&Function2, 1, std::placeholders::_1))); + + runLua("result = Function (12)"); + ASSERT_TRUE(result().isNumber()); + ASSERT_EQ(13, result()); + } +} + +TEST_F(NamespaceTests, StdBindFrontFunctions) +{ + { + luabridge::getGlobalNamespace(L).addFunction("Function", std::bind_front(&Function, 1)); + + runLua("result = Function ()"); + ASSERT_TRUE(result().isNumber()); + ASSERT_EQ(1, result()); + } + + { + luabridge::getGlobalNamespace(L).addFunction("Function", + luabridge::bind_front(&Function2, 1)); + + runLua("result = Function (12)"); + ASSERT_TRUE(result().isNumber()); + ASSERT_EQ(13, result()); + } +} + TEST_F(NamespaceTests, CapturingLambdas) { int x = 30; From f587d4b2dc21fce46747d1d985b8a57f97356e73 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Tue, 5 May 2026 00:30:18 +0200 Subject: [PATCH 3/7] Add support for bind_back too --- Source/LuaBridge/detail/FuncTraits.h | 170 +++++++++++++++++-- Tests/Source/ClassTests.cpp | 244 +++++++++++++++++++++++++++ Tests/Source/LuaRefTests.cpp | 36 ++++ Tests/Source/NamespaceTests.cpp | 45 +++++ 4 files changed, 484 insertions(+), 11 deletions(-) diff --git a/Source/LuaBridge/detail/FuncTraits.h b/Source/LuaBridge/detail/FuncTraits.h index 916328cf..f808a7d3 100644 --- a/Source/LuaBridge/detail/FuncTraits.h +++ b/Source/LuaBridge/detail/FuncTraits.h @@ -609,6 +609,91 @@ struct tuple_drop_first<0, Tuple> template using tuple_drop_first_t = typename tuple_drop_first::type; +//================================================================================================= +/** + * @brief Prepend a type to a tuple. + */ +template +struct tuple_prepend; + +template +struct tuple_prepend> +{ + using type = std::tuple; +}; + +template +using tuple_prepend_t = typename tuple_prepend::type; + +//================================================================================================= +/** + * @brief Take only the first N types from a tuple (uses an accumulator to avoid ambiguity). + */ +template > +struct tuple_take_first_impl +{ + using type = Accum; +}; + +template +struct tuple_take_first_impl, std::tuple> +{ + using type = typename tuple_take_first_impl, std::tuple>::type; +}; + +template +struct tuple_take_first_impl<0, std::tuple, std::tuple> +{ + using type = std::tuple; +}; + +template +using tuple_take_first_t = typename tuple_take_first_impl::type; + +//================================================================================================= +/** + * @brief Extracts the class type from a member function pointer. + */ +template +struct member_function_class; + +template +struct member_function_class { using type = C; }; + +template +struct member_function_class { using type = const C; }; + +template +struct member_function_class { using type = C; }; + +template +struct member_function_class { using type = const C; }; + +template +using member_function_class_t = typename member_function_class::type; + +//================================================================================================= +/** + * @brief Computes the leading argument tuple for bind_back: for member function pointers, + * prepends ClassType* to the explicit remaining args; for all other callables, returns + * the explicit remaining args unchanged. + */ +template +struct bind_back_leading_impl +{ + using type = ExplicitRemaining; +}; + +template +struct bind_back_leading_impl +{ + using type = tuple_prepend_t*, ExplicitRemaining>; +}; + +template +using bind_back_leading_t = + typename bind_back_leading_impl>::type; + //================================================================================================= /** * @brief Internal storage for luabridge::bind_front — exposes a non-template operator() so that @@ -645,16 +730,7 @@ struct bind_front_wrapper, Fn, BoundArgs...> * types cannot be resolved at compile time without an explicit std::function cast. This * wrapper stores the callable and its leading bound arguments, then exposes a concrete * non-template operator() whose parameter types are derived directly from the underlying - * callable's signature. This lets LuaBridge register the result with addFunction / addProperty / - * addMethod without any extra annotation. - * - * @code - * // Instead of: - * ns.addFunction("add", std::function(std::bind_front(&add2, 10))); - * - * // Write: - * ns.addFunction("add", luabridge::bind_front(&add2, 10)); - * @endcode + * callable's signature. * * For member function pointers the implicit object argument consumed by std::invoke is not * counted as part of the remaining (Lua-visible) parameter list, matching std::bind_front @@ -672,7 +748,7 @@ auto bind_front(F&& f, BoundArgs&&... args) using Fn = std::decay_t; using FnTraits = detail::function_traits; - static constexpr std::size_t skip = FnTraits::is_member ? 1u : 0u; + static constexpr std::size_t skip = std::is_member_function_pointer_v ? 1u : 0u; static constexpr std::size_t num_effective_bound = sizeof...(BoundArgs) - skip; using remaining = detail::tuple_drop_first_t; @@ -682,4 +758,76 @@ auto bind_front(F&& f, BoundArgs&&... args) std::forward(f), std::forward(args)...); } +//================================================================================================= +namespace detail { + +/** + * @brief Internal storage for luabridge::bind_back — exposes a non-template operator() so that + * function_traits can statically resolve result_type and argument_types. + * + * LeadingArgsTuple is the tuple of arguments that the caller must provide; BoundArgs are + * the trailing arguments captured at bind time. For member function pointers, the class + * pointer is included as the first element of LeadingArgsTuple. + */ +template +struct bind_back_wrapper; + +template +struct bind_back_wrapper, Fn, BoundArgs...> +{ + Fn fn_; + std::tuple bound_; + + template + bind_back_wrapper(F&& f, BA&&... ba) + : fn_(std::forward(f)), bound_(std::forward(ba)...) + { + } + + R operator()(Leading... args) const + { + return std::apply([&](const auto&... ba) { return std::invoke(fn_, args..., ba...); }, bound_); + } +}; + +} // namespace detail + +//================================================================================================= +/** + * @brief Drop-in replacement for std::bind_back with statically introspectable argument types. + * + * Stores the callable and its trailing bound arguments, then exposes a concrete non-template + * operator() whose parameter types are derived directly from the underlying callable's signature. + * This lets LuaBridge register the result with addFunction / addStaticFunction without any extra + * annotation. + * + * For member function pointers the class pointer is automatically prepended to the remaining + * (Lua-visible) parameter list so that LuaBridge can dispatch it as a proxy member function. + * + * @tparam F Callable type (function pointer, member function pointer, functor). + * @tparam BoundArgs Trailing argument types to bind. + * @param f The callable to wrap. + * @param args Trailing arguments forwarded into the wrapper by value. + * @return A callable object whose operator() accepts the remaining (leading) arguments. + */ +template +auto bind_back(F&& f, BoundArgs&&... args) +{ + using Fn = std::decay_t; + using FnTraits = detail::function_traits; + + static constexpr std::size_t num_explicit = FnTraits::arity; + static constexpr std::size_t num_bound = sizeof...(BoundArgs); + static constexpr std::size_t num_remaining = num_explicit - num_bound; + + using explicit_remaining = detail::tuple_take_first_t; + + using leading = detail::bind_back_leading_t; + + using R = typename FnTraits::result_type; + + return detail::bind_back_wrapper...>( + std::forward(f), std::forward(args)...); +} + } // namespace luabridge diff --git a/Tests/Source/ClassTests.cpp b/Tests/Source/ClassTests.cpp index 131ea54a..cfeca7cd 100644 --- a/Tests/Source/ClassTests.cpp +++ b/Tests/Source/ClassTests.cpp @@ -840,6 +840,36 @@ int proxyCFunctionState(lua_State* L) return 1; } + +template +T proxyFunctionWithBoundOffset(int offset, Class* object, T value) +{ + return value + offset; +} + +template +T proxyConstFunctionWithBoundOffset(int offset, const Class* object, T value) +{ + return value + offset; +} + +template +T proxyFunctionWithTrailingOffset(Class* object, T value, int offset) +{ + return value + offset; +} + +template +T proxyConstFunctionWithTrailingOffset(const Class* object, T value, int offset) +{ + return value + offset; +} + +template +T staticFunctionAdd(T a, T b) +{ + return a + b; +} } // namespace TEST_F(ClassFunctions, ClassWithTemplateMembers) @@ -1137,6 +1167,162 @@ TEST_F(ClassFunctions, ConstStdFunctions) ASSERT_TRUE(data.expired()); } +TEST_F(ClassFunctions, BindFrontProxyFunctions) +{ + using Int = Class; + + luabridge::getGlobalNamespace(L) + .beginClass("Int") + .addFunction("method", luabridge::bind_front(&proxyFunctionWithBoundOffset, 100)) + .endClass(); + + addHelperFunctions(L); + + runLua("result = returnRef ():method (5)"); + ASSERT_EQ(105, result()); + + runLua("result = returnConstRef ().method"); // Don't call, just get + ASSERT_TRUE(result().isNil()); + + runLua("result = returnPtr ():method (2)"); + ASSERT_EQ(102, result()); + + runLua("result = returnConstPtr ().method"); // Don't call, just get + ASSERT_TRUE(result().isNil()); + + runLua("result = returnValue ():method (3)"); + ASSERT_EQ(103, result()); +} + +TEST_F(ClassFunctions, BindFrontConstProxyFunctions) +{ + using Int = Class; + + luabridge::getGlobalNamespace(L) + .beginClass("Int") + .addFunction("constMethod", luabridge::bind_front(&proxyConstFunctionWithBoundOffset, 200)) + .endClass(); + + addHelperFunctions(L); + + runLua("result = returnRef ():constMethod (5)"); + ASSERT_EQ(205, result()); + + runLua("result = returnConstRef ():constMethod (10)"); + ASSERT_EQ(210, result()); + + runLua("result = returnPtr ():constMethod (3)"); + ASSERT_EQ(203, result()); + + runLua("result = returnConstPtr ():constMethod (4)"); + ASSERT_EQ(204, result()); + + runLua("result = returnValue ():constMethod (1)"); + ASSERT_EQ(201, result()); +} + +TEST_F(ClassFunctions, BindBackProxyFunctions) +{ + using Int = Class; + + luabridge::getGlobalNamespace(L) + .beginClass("Int") + .addFunction("method", luabridge::bind_back(&proxyFunctionWithTrailingOffset, 100)) + .endClass(); + + addHelperFunctions(L); + + runLua("result = returnRef ():method (5)"); + ASSERT_EQ(105, result()); + + runLua("result = returnConstRef ().method"); // Don't call, just get + ASSERT_TRUE(result().isNil()); + + runLua("result = returnPtr ():method (2)"); + ASSERT_EQ(102, result()); + + runLua("result = returnConstPtr ().method"); // Don't call, just get + ASSERT_TRUE(result().isNil()); + + runLua("result = returnValue ():method (3)"); + ASSERT_EQ(103, result()); +} + +TEST_F(ClassFunctions, BindBackConstProxyFunctions) +{ + using Int = Class; + + luabridge::getGlobalNamespace(L) + .beginClass("Int") + .addFunction("constMethod", luabridge::bind_back(&proxyConstFunctionWithTrailingOffset, 200)) + .endClass(); + + addHelperFunctions(L); + + runLua("result = returnRef ():constMethod (5)"); + ASSERT_EQ(205, result()); + + runLua("result = returnConstRef ():constMethod (10)"); + ASSERT_EQ(210, result()); + + runLua("result = returnPtr ():constMethod (3)"); + ASSERT_EQ(203, result()); + + runLua("result = returnConstPtr ():constMethod (4)"); + ASSERT_EQ(204, result()); + + runLua("result = returnValue ():constMethod (1)"); + ASSERT_EQ(201, result()); +} + +TEST_F(ClassFunctions, BindBackMemberFunctions) +{ + using Int = Class; + + luabridge::getGlobalNamespace(L) + .beginClass("Int") + .addFunction("method", luabridge::bind_back(&Int::method, 7)) + .endClass(); + + addHelperFunctions(L); + + runLua("result = returnRef ():method ()"); + ASSERT_EQ(7, result()); + + runLua("result = returnPtr ():method ()"); + ASSERT_EQ(7, result()); + + runLua("result = returnValue ():method ()"); + ASSERT_EQ(7, result()); +} + +TEST_F(ClassFunctions, BindBackConstMemberFunctions) +{ + using Int = Class; + + luabridge::getGlobalNamespace(L) + .beginClass("Int") + .addFunction("constMethod", luabridge::bind_back(&Int::constMethod, 9)) + .endClass(); + + addHelperFunctions(L); + + runLua("result = returnRef ():constMethod ()"); + ASSERT_EQ(9, result()); + + runLua("result = returnConstRef ():constMethod ()"); + ASSERT_EQ(9, result()); + + runLua("result = returnPtr ():constMethod ()"); + ASSERT_EQ(9, result()); + + runLua("result = returnConstPtr ():constMethod ()"); + ASSERT_EQ(9, result()); + + runLua("result = returnValue ():constMethod ()"); + ASSERT_EQ(9, result()); +} + struct ClassProperties : ClassTests { }; @@ -1791,6 +1977,64 @@ TEST_F(ClassStaticFunctions, StdFunctions) ASSERT_EQ(35, result().data); } +TEST_F(ClassStaticFunctions, BindFrontFunctions) +{ + using Int = Class; + + luabridge::getGlobalNamespace(L) + .beginClass("Int") + .addStaticFunction("add", luabridge::bind_front(&staticFunctionAdd, 10)) + .endClass(); + + runLua("result = Int.add (32)"); + ASSERT_EQ(42, result()); +} + +TEST_F(ClassStaticFunctions, BindFrontMemberFunctionBoundObject) +{ + using Int = Class; + + Int myObject(42); + + luabridge::getGlobalNamespace(L) + .beginClass("Int") + .addStaticFunction("getData", luabridge::bind_front(&Int::getData, &myObject)) + .addStaticFunction("method", luabridge::bind_front(&Int::method, &myObject)) + .endClass(); + + runLua("result = Int.getData ()"); + ASSERT_EQ(42, result()); + + runLua("result = Int.method (7)"); + ASSERT_EQ(7, result()); +} + +TEST_F(ClassStaticFunctions, BindBackFunctions) +{ + using Int = Class; + + luabridge::getGlobalNamespace(L) + .beginClass("Int") + .addStaticFunction("add", luabridge::bind_back(&staticFunctionAdd, 10)) + .endClass(); + + runLua("result = Int.add (32)"); + ASSERT_EQ(42, result()); +} + +TEST_F(ClassStaticFunctions, BindBackFunctionsFullyBound) +{ + using Int = Class; + + luabridge::getGlobalNamespace(L) + .beginClass("Int") + .addStaticFunction("getFortyTwo", luabridge::bind_back(&staticFunctionAdd, 10, 32)) + .endClass(); + + runLua("result = Int.getFortyTwo ()"); + ASSERT_EQ(42, result()); +} + struct ClassStaticProperties : ClassTests { }; diff --git a/Tests/Source/LuaRefTests.cpp b/Tests/Source/LuaRefTests.cpp index e230ebc8..72fd8f12 100644 --- a/Tests/Source/LuaRefTests.cpp +++ b/Tests/Source/LuaRefTests.cpp @@ -9,6 +9,10 @@ #include +namespace { +int addInts(int a, int b) { return a + b; } +} // namespace + struct LuaRefTests : TestBase { }; @@ -913,6 +917,38 @@ TEST_F(LuaRefTests, RegisterLambdaInFunction) ASSERT_EQ(1 + 10 + 100 + 3, result()); } +TEST_F(LuaRefTests, RegisterBindFrontInNewFunction) +{ + luabridge::setGlobal(L, luabridge::newFunction(L, luabridge::bind_front(&addInts, 10)), "addTen"); + + runLua("result = addTen (32)"); + ASSERT_EQ(42, result()); +} + +TEST_F(LuaRefTests, RegisterBindFrontLambdaInNewFunction) +{ + luabridge::setGlobal(L, luabridge::newFunction(L, luabridge::bind_front([](int a, int b) { return a + b; }, 10)), "addTen"); + + runLua("result = addTen (32)"); + ASSERT_EQ(42, result()); +} + +TEST_F(LuaRefTests, RegisterBindBackInNewFunction) +{ + luabridge::setGlobal(L, luabridge::newFunction(L, luabridge::bind_back(&addInts, 10)), "addTen"); + + runLua("result = addTen (32)"); + ASSERT_EQ(42, result()); +} + +TEST_F(LuaRefTests, RegisterBindBackLambdaInNewFunction) +{ + luabridge::setGlobal(L, luabridge::newFunction(L, luabridge::bind_back([](int a, int b) { return a + b; }, 10)), "addTen"); + + runLua("result = addTen (32)"); + ASSERT_EQ(42, result()); +} + TEST_F(LuaRefTests, HookTesting) { std::map hooklist; diff --git a/Tests/Source/NamespaceTests.cpp b/Tests/Source/NamespaceTests.cpp index 412d06fe..47135d5a 100644 --- a/Tests/Source/NamespaceTests.cpp +++ b/Tests/Source/NamespaceTests.cpp @@ -668,6 +668,51 @@ TEST_F(NamespaceTests, StdBindFrontFunctions) } } +TEST_F(NamespaceTests, BindFrontCapturingLambdas) +{ + int x = 30; + + luabridge::getGlobalNamespace(L).addFunction("Function", + luabridge::bind_front([x](int offset, int v) -> int { return x + offset + v; }, 2)); + + runLua("result = Function (10)"); + ASSERT_TRUE(result().isNumber()); + ASSERT_EQ(42, result()); +} + +TEST_F(NamespaceTests, BindBackFunctions) +{ + { + luabridge::getGlobalNamespace(L).addFunction("Function", + luabridge::bind_back(&Function, 1)); + + runLua("result = Function ()"); + ASSERT_TRUE(result().isNumber()); + ASSERT_EQ(1, result()); + } + + { + luabridge::getGlobalNamespace(L).addFunction("Function", + luabridge::bind_back(&Function2, 1)); + + runLua("result = Function (12)"); + ASSERT_TRUE(result().isNumber()); + ASSERT_EQ(13, result()); + } +} + +TEST_F(NamespaceTests, BindBackCapturingLambdas) +{ + int x = 30; + + luabridge::getGlobalNamespace(L).addFunction("Function", + luabridge::bind_back([x](int v, int offset) -> int { return x + offset + v; }, 2)); + + runLua("result = Function (10)"); + ASSERT_TRUE(result().isNumber()); + ASSERT_EQ(42, result()); +} + TEST_F(NamespaceTests, CapturingLambdas) { int x = 30; From e2ce77e101d4d8730b6987097c6b5f370f3f64f3 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Tue, 5 May 2026 00:32:19 +0200 Subject: [PATCH 4/7] Updated manual --- Manual.md | 56 +++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/Manual.md b/Manual.md index 945c5caf..1931ef4a 100644 --- a/Manual.md +++ b/Manual.md @@ -33,7 +33,8 @@ Contents * [2.4 - Property Member Proxies](#24---property-member-proxies) * [2.5 - Function Member Proxies](#25---function-member-proxies) * [2.5.1 - Partial Application with luabridge::bind_front](#251---partial-application-with-luabridgebind_front) - * [2.5.2 - Function Overloading](#252---function-overloading) + * [2.5.2 - Partial Application with luabridge::bind_back](#252---partial-application-with-luabridgebind_back) + * [2.5.3 - Function Overloading](#253---function-overloading) * [2.6 - Constructors](#26---constructors) * [2.6.1 - Constructor Proxies](#261---constructor-proxies) * [2.6.2 - Constructor Factories](#262---constructor-factories) @@ -621,20 +622,59 @@ ns.addFunction ("add10", std::function (std::bind_front (&add, 10))); ns.addFunction ("add10", luabridge::bind_front (&add, 10)); ``` -It also works with member function pointers, where the bound object is not counted as a Lua-visible parameter: +It also works with member function pointers. The bound object is not counted as a Lua-visible parameter, so `bind_front` turns a member function into a plain callable that Lua sees as a regular function: ```cpp -struct Vec { float coord [3]; }; -float getCoord (const Vec* v, int i) { return v->coord [i]; } +struct Vec { float coord [3]; float getCoord (int i) const { return coord [i]; } }; + +Vec v; +// Bind v as the object; Lua supplies the index +ns.addFunction ("getCoord", luabridge::bind_front (&Vec::getCoord, &v)); +// Lua: getCoord(0) == v.coord[0] +``` + +It also accepts lambdas: + +```cpp +int base = 10; +ns.addFunction ("addBase", luabridge::bind_front ([base](int offset, int v) { return base + offset + v; }, 2)); +// Lua: addBase(30) == 42 +``` + +### 2.5.2 - Partial Application with luabridge::bind_back + +`luabridge::bind_back` is the trailing-argument counterpart to `bind_front`: it pre-binds the **last** arguments of a callable, leaving the **leading** arguments for Lua to supply. + +```cpp +int add (int a, int b) { return a + b; } + +// bind_back fixes the trailing argument; Lua supplies the leading one +ns.addFunction ("add10", luabridge::bind_back (&add, 10)); +// Lua: add10(32) == 42 +``` + +For member function pointers, `bind_back` automatically prepends the class pointer to the remaining (Lua-visible) parameter list so that LuaBridge can dispatch it as a proxy function. This lets you pre-bind a method's trailing arguments while the object still comes from Lua: + +```cpp +struct Vec { float coord [3]; float getCoord (int i) const { return coord [i]; } }; ns.beginClass ("Vec") - .addFunction ("x", luabridge::bind_front (&getCoord, 0)) - .addFunction ("y", luabridge::bind_front (&getCoord, 1)) - .addFunction ("z", luabridge::bind_front (&getCoord, 2)) + .addFunction ("x", luabridge::bind_back (&Vec::getCoord, 0)) + .addFunction ("y", luabridge::bind_back (&Vec::getCoord, 1)) + .addFunction ("z", luabridge::bind_back (&Vec::getCoord, 2)) .endClass (); +// Lua: v:x() returns v.coord[0], v:y() returns v.coord[1], etc. +``` + +`bind_back` also accepts lambdas: + +```cpp +int base = 10; +ns.addFunction ("addBase", luabridge::bind_back ([base](int v, int offset) { return base + offset + v; }, 2)); +// Lua: addBase(30) == 42 ``` -### 2.5.2 - Function Overloading +### 2.5.3 - Function Overloading When specifying more than one method to the `addFunction` or `addStaticFunction` of both `Namespace` and `Class`, those overloads will be invoked in case of a call. Only overloads that have matched arguments arity will be considered, and they will be tried from first to last until the call succeeds. From 053451793375f3909913fc1174806b550ea5b731 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Tue, 5 May 2026 00:37:51 +0200 Subject: [PATCH 5/7] Add support for proper std types with custom allocators --- Source/LuaBridge/List.h | 6 +++--- Source/LuaBridge/Map.h | 6 +++--- Source/LuaBridge/Set.h | 6 +++--- Source/LuaBridge/UnorderedMap.h | 6 +++--- Source/LuaBridge/Vector.h | 6 +++--- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Source/LuaBridge/List.h b/Source/LuaBridge/List.h index 14f3d607..6c4c1dd0 100644 --- a/Source/LuaBridge/List.h +++ b/Source/LuaBridge/List.h @@ -15,10 +15,10 @@ namespace luabridge { /** * @brief Stack specialization for `std::array`. */ -template -struct Stack> +template +struct Stack> { - using Type = std::list; + using Type = std::list; [[nodiscard]] static Result push(lua_State* L, const Type& list) { diff --git a/Source/LuaBridge/Map.h b/Source/LuaBridge/Map.h index 3a0871be..e4ac42de 100644 --- a/Source/LuaBridge/Map.h +++ b/Source/LuaBridge/Map.h @@ -15,10 +15,10 @@ namespace luabridge { /** * @brief Stack specialization for `std::map`. */ -template -struct Stack> +template +struct Stack> { - using Type = std::map; + using Type = std::map; [[nodiscard]] static Result push(lua_State* L, const Type& map) { diff --git a/Source/LuaBridge/Set.h b/Source/LuaBridge/Set.h index b5315e0c..ccb92d7b 100644 --- a/Source/LuaBridge/Set.h +++ b/Source/LuaBridge/Set.h @@ -14,10 +14,10 @@ namespace luabridge { /** * @brief Stack specialization for `std::set`. */ -template -struct Stack> +template +struct Stack> { - using Type = std::set; + using Type = std::set; [[nodiscard]] static Result push(lua_State* L, const Type& set) { diff --git a/Source/LuaBridge/UnorderedMap.h b/Source/LuaBridge/UnorderedMap.h index 65fc9c17..cef355ca 100644 --- a/Source/LuaBridge/UnorderedMap.h +++ b/Source/LuaBridge/UnorderedMap.h @@ -15,10 +15,10 @@ namespace luabridge { /** * @brief Stack specialization for `std::unordered_map`. */ -template -struct Stack> +template +struct Stack> { - using Type = std::unordered_map; + using Type = std::unordered_map; [[nodiscard]] static Result push(lua_State* L, const Type& map) { diff --git a/Source/LuaBridge/Vector.h b/Source/LuaBridge/Vector.h index 5376f346..4beb3feb 100644 --- a/Source/LuaBridge/Vector.h +++ b/Source/LuaBridge/Vector.h @@ -15,10 +15,10 @@ namespace luabridge { /** * @brief Stack specialization for `std::vector`. */ -template -struct Stack> +template +struct Stack> { - using Type = std::vector; + using Type = std::vector; [[nodiscard]] static Result push(lua_State* L, const Type& vector) { From 99c61e12f40141254434f252cabab4b61fa30a88 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Tue, 5 May 2026 09:20:07 +0200 Subject: [PATCH 6/7] Add variant support too --- Source/LuaBridge/Variant.h | 85 ++++++++++++++++++ Tests/CMakeLists.txt | 1 + Tests/Source/VariantTests.cpp | 160 ++++++++++++++++++++++++++++++++++ 3 files changed, 246 insertions(+) create mode 100644 Source/LuaBridge/Variant.h create mode 100644 Tests/Source/VariantTests.cpp diff --git a/Source/LuaBridge/Variant.h b/Source/LuaBridge/Variant.h new file mode 100644 index 00000000..b5425273 --- /dev/null +++ b/Source/LuaBridge/Variant.h @@ -0,0 +1,85 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2026, kunitoki +// SPDX-License-Identifier: MIT + +#pragma once + +#include "detail/Stack.h" + +#include +#include + +namespace luabridge { + +//================================================================================================= +/** + * @brief Stack specialization for `std::variant`. + */ +template +struct Stack> +{ + using Type = std::variant; + + [[nodiscard]] static Result push(lua_State* L, const Type& variant) + { + return std::visit([L](const auto& value) { return Stack>::push(L, value); }, variant); + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + return tryGet(L, index); + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + return (Stack::isInstance(L, index) || ...); + } + +private: + template + [[nodiscard]] static TypeResult tryGet(lua_State* L, int index) + { + if (auto value = Stack::get(L, index)) + return Type{ std::in_place_type, std::move(*value) }; + + if constexpr (sizeof...(Rest) > 0) + return tryGet(L, index); + + return makeErrorCode(ErrorCode::InvalidTypeCast); + } +}; + +/** + * @brief Stack specialization for `std::monostate`. + */ +template <> +struct Stack +{ + using Type = std::monostate; + + [[nodiscard]] static Result push(lua_State* L, const Type&) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 1)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + lua_pushnil(L); + return {}; + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + if (lua_isnoneornil(L, index)) + return Type{}; + + return makeErrorCode(ErrorCode::InvalidTypeCast); + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + return lua_isnoneornil(L, index); + } +}; + +} // namespace luabridge diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 414d3a9a..90196775 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -59,6 +59,7 @@ set (LUABRIDGE_TEST_SOURCE_FILES Source/TestsMain.cpp Source/UnorderedMapTests.cpp Source/UserdataTests.cpp + Source/VariantTests.cpp Source/VectorTests.cpp ) diff --git a/Tests/Source/VariantTests.cpp b/Tests/Source/VariantTests.cpp new file mode 100644 index 00000000..591593d1 --- /dev/null +++ b/Tests/Source/VariantTests.cpp @@ -0,0 +1,160 @@ +// https://github.com/kunitoki/LuaBridge3 +// Copyright 2026, kunitoki +// SPDX-License-Identifier: MIT + +#include "TestBase.h" + +#include "LuaBridge/Variant.h" + +#include +#include + +namespace { +using TestVariant = std::variant; + +std::string describeVariant(const TestVariant& value) +{ + if (const auto* intValue = std::get_if(&value)) + return "int:" + std::to_string(*intValue); + + return "string:" + std::get(value); +} + +TestVariant echoVariant(const TestVariant& value) +{ + return value; +} +} // namespace + +struct VariantTests : TestBase +{ +}; + +TEST_F(VariantTests, PushIntAlternative) +{ + TestVariant value = 42; + + ASSERT_TRUE(luabridge::push(L, value)); + + EXPECT_EQ(LUA_TNUMBER, lua_type(L, -1)); + EXPECT_EQ(42, lua_tointeger(L, -1)); +} + +TEST_F(VariantTests, PushStringAlternative) +{ + TestVariant value = std::string("hello"); + + ASSERT_TRUE(luabridge::push(L, value)); + + EXPECT_EQ(LUA_TSTRING, lua_type(L, -1)); + EXPECT_STREQ("hello", lua_tostring(L, -1)); +} + +TEST_F(VariantTests, GetIntAlternative) +{ + lua_pushinteger(L, 42); + + auto result = luabridge::Stack::get(L, -1); + + ASSERT_TRUE(result); + ASSERT_TRUE(std::holds_alternative(*result)); + EXPECT_EQ(42, std::get(*result)); +} + +TEST_F(VariantTests, GetStringAlternative) +{ + lua_pushstring(L, "hello"); + + auto result = luabridge::Stack::get(L, -1); + + ASSERT_TRUE(result); + ASSERT_TRUE(std::holds_alternative(*result)); + EXPECT_EQ("hello", std::get(*result)); +} + +TEST_F(VariantTests, GetInvalidType) +{ + lua_createtable(L, 0, 0); + + auto result = luabridge::Stack::get(L, -1); + + ASSERT_FALSE(result); + EXPECT_EQ(luabridge::ErrorCode::InvalidTypeCast, result.error()); +} + +TEST_F(VariantTests, GetMonostateAlternative) +{ + using MaybeInt = std::variant; + + lua_pushnil(L); + + auto result = luabridge::Stack::get(L, -1); + + ASSERT_TRUE(result); + EXPECT_TRUE(std::holds_alternative(*result)); +} + +TEST_F(VariantTests, PushMonostateAlternative) +{ + using MaybeInt = std::variant; + + MaybeInt value = std::monostate{}; + + ASSERT_TRUE(luabridge::push(L, value)); + EXPECT_TRUE(lua_isnil(L, -1)); +} + +TEST_F(VariantTests, IsInstance) +{ + lua_pushinteger(L, 42); + EXPECT_TRUE(luabridge::isInstance(L, -1)); + + lua_pop(L, 1); + lua_pushstring(L, "hello"); + EXPECT_TRUE(luabridge::isInstance(L, -1)); + + lua_pop(L, 1); + lua_createtable(L, 0, 0); + EXPECT_FALSE(luabridge::isInstance(L, -1)); +} + +TEST_F(VariantTests, StackOverflow) +{ + exhaustStackSpace(); + + TestVariant value = 42; + + ASSERT_FALSE(luabridge::push(L, value)); +} + +TEST_F(VariantTests, PassFromLua) +{ + luabridge::getGlobalNamespace(L) + .addFunction("describeVariant", &describeVariant); + + resetResult(); + runLua("result = describeVariant(42)"); + + EXPECT_EQ("int:42", result()); + + resetResult(); + runLua("result = describeVariant('hello')"); + + EXPECT_EQ("string:hello", result()); +} + +TEST_F(VariantTests, ReturnToLua) +{ + luabridge::getGlobalNamespace(L) + .addFunction("echoVariant", &echoVariant); + + resetResult(); + runLua("result = echoVariant(42)"); + + EXPECT_EQ(42, result()); + + resetResult(); + runLua("result = echoVariant('hello')"); + + EXPECT_EQ("hello", result()); +} From f0f50a67dcf1044485899cbded8633e4db65c181 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Tue, 5 May 2026 09:28:13 +0200 Subject: [PATCH 7/7] Updated amalgamated header --- Distribution/LuaBridge/LuaBridge.h | 314 +++++++++++++++++++++++++++-- 1 file changed, 298 insertions(+), 16 deletions(-) diff --git a/Distribution/LuaBridge/LuaBridge.h b/Distribution/LuaBridge/LuaBridge.h index f5520a6c..0b75d4e5 100644 --- a/Distribution/LuaBridge/LuaBridge.h +++ b/Distribution/LuaBridge/LuaBridge.h @@ -34,6 +34,7 @@ #include #include #include +#include #include @@ -299,8 +300,32 @@ struct function_traits_impl : }; #endif +template +struct has_call_operator : std::false_type +{ +}; + template -struct functor_traits_impl : function_traits_impl +struct has_call_operator> : std::true_type +{ +}; + +template +inline static constexpr bool has_call_operator_v = has_call_operator::value; + +template +struct functor_traits_impl +{ +}; + +template +struct functor_traits_impl>> : function_traits_impl +{ +}; + +template +struct functor_traits_impl && std::is_invocable_v>> + : function_traits_base> { }; @@ -311,6 +336,19 @@ struct function_traits : std::conditional_t, { }; +template , class = void> +struct has_function_traits : std::false_type +{ +}; + +template +struct has_function_traits::result_type, typename function_traits::argument_types>> : std::true_type +{ +}; + +template +inline static constexpr bool has_function_traits_v = has_function_traits::value; + template struct function_argument_or_void { @@ -356,6 +394,12 @@ struct is_callable> static constexpr bool value = true; }; +template +struct is_callable && !has_call_operator_v && has_function_traits_v>> +{ + static constexpr bool value = true; +}; + template struct is_callable && std::is_function_v>>> { @@ -539,7 +583,172 @@ struct remove_first_type> template using remove_first_type_t = typename remove_first_type::type; +template +struct tuple_drop_first +{ + using type = typename tuple_drop_first>::type; +}; + +template +struct tuple_drop_first<0, Tuple> +{ + using type = Tuple; +}; + +template +using tuple_drop_first_t = typename tuple_drop_first::type; + +template +struct tuple_prepend; + +template +struct tuple_prepend> +{ + using type = std::tuple; +}; + +template +using tuple_prepend_t = typename tuple_prepend::type; + +template > +struct tuple_take_first_impl +{ + using type = Accum; +}; + +template +struct tuple_take_first_impl, std::tuple> +{ + using type = typename tuple_take_first_impl, std::tuple>::type; +}; + +template +struct tuple_take_first_impl<0, std::tuple, std::tuple> +{ + using type = std::tuple; +}; + +template +using tuple_take_first_t = typename tuple_take_first_impl::type; + +template +struct member_function_class; + +template +struct member_function_class { using type = C; }; + +template +struct member_function_class { using type = const C; }; + +template +struct member_function_class { using type = C; }; + +template +struct member_function_class { using type = const C; }; + +template +using member_function_class_t = typename member_function_class::type; + +template +struct bind_back_leading_impl +{ + using type = ExplicitRemaining; +}; + +template +struct bind_back_leading_impl +{ + using type = tuple_prepend_t*, ExplicitRemaining>; +}; + +template +using bind_back_leading_t = + typename bind_back_leading_impl>::type; + +template +struct bind_front_wrapper; + +template +struct bind_front_wrapper, Fn, BoundArgs...> +{ + Fn fn_; + std::tuple bound_; + + template + bind_front_wrapper(F&& f, BA&&... ba) + : fn_(std::forward(f)), bound_(std::forward(ba)...) + { + } + + R operator()(Remaining... args) const + { + return std::apply([&](const auto&... ba) { return std::invoke(fn_, ba..., args...); }, bound_); + } +}; + } + +template +auto bind_front(F&& f, BoundArgs&&... args) +{ + using Fn = std::decay_t; + using FnTraits = detail::function_traits; + + static constexpr std::size_t skip = std::is_member_function_pointer_v ? 1u : 0u; + static constexpr std::size_t num_effective_bound = sizeof...(BoundArgs) - skip; + + using remaining = detail::tuple_drop_first_t; + using R = typename FnTraits::result_type; + + return detail::bind_front_wrapper...>( + std::forward(f), std::forward(args)...); +} + +namespace detail { + +template +struct bind_back_wrapper; + +template +struct bind_back_wrapper, Fn, BoundArgs...> +{ + Fn fn_; + std::tuple bound_; + + template + bind_back_wrapper(F&& f, BA&&... ba) + : fn_(std::forward(f)), bound_(std::forward(ba)...) + { + } + + R operator()(Leading... args) const + { + return std::apply([&](const auto&... ba) { return std::invoke(fn_, args..., ba...); }, bound_); + } +}; + +} + +template +auto bind_back(F&& f, BoundArgs&&... args) +{ + using Fn = std::decay_t; + using FnTraits = detail::function_traits; + + static constexpr std::size_t num_explicit = FnTraits::arity; + static constexpr std::size_t num_bound = sizeof...(BoundArgs); + static constexpr std::size_t num_remaining = num_explicit - num_bound; + + using explicit_remaining = detail::tuple_take_first_t; + + using leading = detail::bind_back_leading_t; + + using R = typename FnTraits::result_type; + + return detail::bind_back_wrapper...>( + std::forward(f), std::forward(args)...); +} + } @@ -6066,10 +6275,10 @@ inline void dumpState(lua_State* L, unsigned maxDepth = 1, std::ostream& stream namespace luabridge { -template -struct Stack> +template +struct Stack> { - using Type = std::list; + using Type = std::list; [[nodiscard]] static Result push(lua_State* L, const Type& list) { @@ -12663,10 +12872,10 @@ ScopeGuard(F&&) -> ScopeGuard; namespace luabridge { -template -struct Stack> +template +struct Stack> { - using Type = std::map; + using Type = std::map; [[nodiscard]] static Result push(lua_State* L, const Type& map) { @@ -12740,10 +12949,10 @@ struct Stack> namespace luabridge { -template -struct Stack> +template +struct Stack> { - using Type = std::set; + using Type = std::set; [[nodiscard]] static Result push(lua_State* L, const Type& set) { @@ -12812,10 +13021,10 @@ struct Stack> namespace luabridge { -template -struct Stack> +template +struct Stack> { - using Type = std::unordered_map; + using Type = std::unordered_map; [[nodiscard]] static Result push(lua_State* L, const Type& map) { @@ -12885,14 +13094,87 @@ struct Stack> // End File: Source/LuaBridge/UnorderedMap.h +// Begin File: Source/LuaBridge/Variant.h + +namespace luabridge { + +template +struct Stack> +{ + using Type = std::variant; + + [[nodiscard]] static Result push(lua_State* L, const Type& variant) + { + return std::visit([L](const auto& value) { return Stack>::push(L, value); }, variant); + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + return tryGet(L, index); + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + return (Stack::isInstance(L, index) || ...); + } + +private: + template + [[nodiscard]] static TypeResult tryGet(lua_State* L, int index) + { + if (auto value = Stack::get(L, index)) + return Type{ std::in_place_type, std::move(*value) }; + + if constexpr (sizeof...(Rest) > 0) + return tryGet(L, index); + + return makeErrorCode(ErrorCode::InvalidTypeCast); + } +}; + +template <> +struct Stack +{ + using Type = std::monostate; + + [[nodiscard]] static Result push(lua_State* L, const Type&) + { +#if LUABRIDGE_SAFE_STACK_CHECKS + if (! lua_checkstack(L, 1)) + return makeErrorCode(ErrorCode::LuaStackOverflow); +#endif + + lua_pushnil(L); + return {}; + } + + [[nodiscard]] static TypeResult get(lua_State* L, int index) + { + if (lua_isnoneornil(L, index)) + return Type{}; + + return makeErrorCode(ErrorCode::InvalidTypeCast); + } + + [[nodiscard]] static bool isInstance(lua_State* L, int index) + { + return lua_isnoneornil(L, index); + } +}; + +} + + +// End File: Source/LuaBridge/Variant.h + // Begin File: Source/LuaBridge/Vector.h namespace luabridge { -template -struct Stack> +template +struct Stack> { - using Type = std::vector; + using Type = std::vector; [[nodiscard]] static Result push(lua_State* L, const Type& vector) {