Skip to content

Commit 975ebb8

Browse files
committed
Add support for a workable bind_front
1 parent c7bfda5 commit 975ebb8

3 files changed

Lines changed: 215 additions & 3 deletions

File tree

‎Manual.md‎

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ Contents
3232
* [2.3.1 - Multiple Inheritance](#231---multiple-inheritance)
3333
* [2.4 - Property Member Proxies](#24---property-member-proxies)
3434
* [2.5 - Function Member Proxies](#25---function-member-proxies)
35-
* [2.5.1 - Function Overloading](#251---function-overloading)
35+
* [2.5.1 - Partial Application with luabridge::bind_front](#251---partial-application-with-luabridgebind_front)
36+
* [2.5.2 - Function Overloading](#252---function-overloading)
3637
* [2.6 - Constructors](#26---constructors)
3738
* [2.6.1 - Constructor Proxies](#261---constructor-proxies)
3839
* [2.6.2 - Constructor Factories](#262---constructor-factories)
@@ -602,7 +603,38 @@ luabridge::getGlobalNamespace (L)
602603
603604
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.
604605
605-
### 2.5.1 - Function Overloading
606+
### 2.5.1 - Partial Application with luabridge::bind_front
607+
608+
`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`:
609+
610+
```cpp
611+
int add (int a, int b) { return a + b; }
612+
613+
// Verbose: explicit signature required
614+
ns.addFunction ("add10", std::function<int(int)> (std::bind_front (&add, 10)));
615+
```
616+
617+
`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:
618+
619+
```cpp
620+
// Concise: types are deduced automatically
621+
ns.addFunction ("add10", luabridge::bind_front (&add, 10));
622+
```
623+
624+
It also works with member function pointers, where the bound object is not counted as a Lua-visible parameter:
625+
626+
```cpp
627+
struct Vec { float coord [3]; };
628+
float getCoord (const Vec* v, int i) { return v->coord [i]; }
629+
630+
ns.beginClass<Vec> ("Vec")
631+
.addFunction ("x", luabridge::bind_front (&getCoord, 0))
632+
.addFunction ("y", luabridge::bind_front (&getCoord, 1))
633+
.addFunction ("z", luabridge::bind_front (&getCoord, 2))
634+
.endClass ();
635+
```
636+
637+
### 2.5.2 - Function Overloading
606638
607639
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.
608640

‎Source/LuaBridge/detail/FuncTraits.h‎

Lines changed: 135 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,8 +195,32 @@ struct function_traits_impl<R (__fastcall C::*)(Args...) const noexcept, true> :
195195
};
196196
#endif
197197

198+
template <class F, class = void>
199+
struct has_call_operator : std::false_type
200+
{
201+
};
202+
203+
template <class F>
204+
struct has_call_operator<F, std::void_t<decltype(&F::operator())>> : std::true_type
205+
{
206+
};
207+
208+
template <class F>
209+
inline static constexpr bool has_call_operator_v = has_call_operator<F>::value;
210+
211+
template <class F, class = void>
212+
struct functor_traits_impl
213+
{
214+
};
215+
216+
template <class F>
217+
struct functor_traits_impl<F, std::enable_if_t<has_call_operator_v<F>>> : function_traits_impl<decltype(&F::operator()), true>
218+
{
219+
};
220+
198221
template <class F>
199-
struct functor_traits_impl : function_traits_impl<decltype(&F::operator()), true>
222+
struct functor_traits_impl<F, std::enable_if_t<!has_call_operator_v<F> && std::is_invocable_v<F&>>>
223+
: function_traits_base<false, false, std::invoke_result_t<F&>>
200224
{
201225
};
202226

@@ -213,6 +237,19 @@ struct function_traits : std::conditional_t<std::is_class_v<F>,
213237
{
214238
};
215239

240+
template <class T, bool IsClass = std::is_class_v<T>, class = void>
241+
struct has_function_traits : std::false_type
242+
{
243+
};
244+
245+
template <class T>
246+
struct has_function_traits<T, true, std::void_t<typename function_traits<T>::result_type, typename function_traits<T>::argument_types>> : std::true_type
247+
{
248+
};
249+
250+
template <class T>
251+
inline static constexpr bool has_function_traits_v = has_function_traits<T>::value;
252+
216253
//=================================================================================================
217254
/**
218255
* @brief Deduces the argument type of a callble object or void in case it has no argument.
@@ -303,6 +340,12 @@ struct is_callable<T, std::void_t<decltype(&T::operator())>>
303340
static constexpr bool value = true;
304341
};
305342

343+
template <class T>
344+
struct is_callable<T, std::enable_if_t<std::is_class_v<T> && !has_call_operator_v<T> && has_function_traits_v<T>>>
345+
{
346+
static constexpr bool value = true;
347+
};
348+
306349
template <class T>
307350
struct is_callable<T, std::enable_if_t<std::is_pointer_v<T> && std::is_function_v<std::remove_pointer_t<T>>>>
308351
{
@@ -547,5 +590,96 @@ struct remove_first_type<std::tuple<T, Ts...>>
547590
template <class T>
548591
using remove_first_type_t = typename remove_first_type<T>::type;
549592

593+
//=================================================================================================
594+
/**
595+
* @brief Drop the first N types from a tuple.
596+
*/
597+
template <std::size_t N, class Tuple>
598+
struct tuple_drop_first
599+
{
600+
using type = typename tuple_drop_first<N - 1, remove_first_type_t<Tuple>>::type;
601+
};
602+
603+
template <class Tuple>
604+
struct tuple_drop_first<0, Tuple>
605+
{
606+
using type = Tuple;
607+
};
608+
609+
template <std::size_t N, class Tuple>
610+
using tuple_drop_first_t = typename tuple_drop_first<N, Tuple>::type;
611+
612+
//=================================================================================================
613+
/**
614+
* @brief Internal storage for luabridge::bind_front — exposes a non-template operator() so that
615+
* function_traits can statically resolve result_type and argument_types.
616+
*/
617+
template <class R, class RemainingArgsTuple, class Fn, class... BoundArgs>
618+
struct bind_front_wrapper;
619+
620+
template <class R, class... Remaining, class Fn, class... BoundArgs>
621+
struct bind_front_wrapper<R, std::tuple<Remaining...>, Fn, BoundArgs...>
622+
{
623+
Fn fn_;
624+
std::tuple<BoundArgs...> bound_;
625+
626+
template <class F, class... BA>
627+
bind_front_wrapper(F&& f, BA&&... ba)
628+
: fn_(std::forward<F>(f)), bound_(std::forward<BA>(ba)...)
629+
{
630+
}
631+
632+
R operator()(Remaining... args) const
633+
{
634+
return std::apply([&](const auto&... ba) { return std::invoke(fn_, ba..., args...); }, bound_);
635+
}
636+
};
637+
550638
} // namespace detail
639+
640+
//=================================================================================================
641+
/**
642+
* @brief Drop-in replacement for std::bind_front with statically introspectable argument types.
643+
*
644+
* std::bind_front returns an object whose operator() is a template, so its argument and result
645+
* types cannot be resolved at compile time without an explicit std::function<Sig> cast. This
646+
* wrapper stores the callable and its leading bound arguments, then exposes a concrete
647+
* non-template operator() whose parameter types are derived directly from the underlying
648+
* callable's signature. This lets LuaBridge register the result with addFunction / addProperty /
649+
* addMethod without any extra annotation.
650+
*
651+
* @code
652+
* // Instead of:
653+
* ns.addFunction("add", std::function<int(int)>(std::bind_front(&add2, 10)));
654+
*
655+
* // Write:
656+
* ns.addFunction("add", luabridge::bind_front(&add2, 10));
657+
* @endcode
658+
*
659+
* For member function pointers the implicit object argument consumed by std::invoke is not
660+
* counted as part of the remaining (Lua-visible) parameter list, matching std::bind_front
661+
* semantics.
662+
*
663+
* @tparam F Callable type (function pointer, member function pointer, functor).
664+
* @tparam BoundArgs Leading argument types to bind.
665+
* @param f The callable to wrap.
666+
* @param args Leading arguments forwarded into the wrapper by value.
667+
* @return A callable object whose operator() accepts the remaining (unbound) arguments.
668+
*/
669+
template <class F, class... BoundArgs>
670+
auto bind_front(F&& f, BoundArgs&&... args)
671+
{
672+
using Fn = std::decay_t<F>;
673+
using FnTraits = detail::function_traits<Fn>;
674+
675+
static constexpr std::size_t skip = FnTraits::is_member ? 1u : 0u;
676+
static constexpr std::size_t num_effective_bound = sizeof...(BoundArgs) - skip;
677+
678+
using remaining = detail::tuple_drop_first_t<num_effective_bound, typename FnTraits::argument_types>;
679+
using R = typename FnTraits::result_type;
680+
681+
return detail::bind_front_wrapper<R, remaining, Fn, std::decay_t<BoundArgs>...>(
682+
std::forward<F>(f), std::forward<BoundArgs>(args)...);
683+
}
684+
551685
} // namespace luabridge

‎Tests/Source/NamespaceTests.cpp‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,12 @@ T Function(T param)
588588
return param;
589589
}
590590

591+
template<class T>
592+
T Function2(T param1, T param2)
593+
{
594+
return param1 + param2;
595+
}
596+
591597
int LuaFunction(lua_State* L)
592598
{
593599
lua_pushnumber(L, 42);
@@ -622,6 +628,46 @@ TEST_F(NamespaceTests, StdFunctions)
622628
ASSERT_EQ(12, result<int>());
623629
}
624630

631+
TEST_F(NamespaceTests, StdBindFunctions)
632+
{
633+
{
634+
luabridge::getGlobalNamespace(L).addFunction("Function", std::bind(&Function<int>, 1));
635+
636+
runLua("result = Function ()");
637+
ASSERT_TRUE(result().isNumber());
638+
ASSERT_EQ(1, result<int>());
639+
}
640+
641+
{
642+
luabridge::getGlobalNamespace(L).addFunction("Function",
643+
std::function<int(int)>(std::bind(&Function2<int>, 1, std::placeholders::_1)));
644+
645+
runLua("result = Function (12)");
646+
ASSERT_TRUE(result().isNumber());
647+
ASSERT_EQ(13, result<int>());
648+
}
649+
}
650+
651+
TEST_F(NamespaceTests, StdBindFrontFunctions)
652+
{
653+
{
654+
luabridge::getGlobalNamespace(L).addFunction("Function", std::bind_front(&Function<int>, 1));
655+
656+
runLua("result = Function ()");
657+
ASSERT_TRUE(result().isNumber());
658+
ASSERT_EQ(1, result<int>());
659+
}
660+
661+
{
662+
luabridge::getGlobalNamespace(L).addFunction("Function",
663+
luabridge::bind_front(&Function2<int>, 1));
664+
665+
runLua("result = Function (12)");
666+
ASSERT_TRUE(result().isNumber());
667+
ASSERT_EQ(13, result<int>());
668+
}
669+
}
670+
625671
TEST_F(NamespaceTests, CapturingLambdas)
626672
{
627673
int x = 30;

0 commit comments

Comments
 (0)