Fix #612: Allow fun<Signature>(&overloaded) for overload disambiguation (#701)

The cheatsheet's "preferred" syntax for binding overloaded free functions,
fun<Ret(Args...)>(&overloaded), only worked on MSVC because the existing
fun<T>(T&&) overload sets T to a function type when called explicitly,
which produces a function rvalue-reference parameter that does not match
a function pointer argument. Standards-conforming compilers (clang, GCC
in some modes) correctly rejected it.

Added two new fun overloads, parameterized on the explicitly-supplied
function-type Signature and using std::type_identity_t to keep that
parameter non-deducible, so they only participate in overload resolution
when Signature is given explicitly. One accepts a free function pointer,
the other a pointer-to-member, enabling both fun<Ret(Args...)>(&free_fn)
and fun<Ret(Args...) cv>(&Class::method) to disambiguate overloads
without resorting to static_cast.

Co-authored-by: leftibot <leftibot@users.noreply.github.com>
This commit is contained in:
leftibot 2026-05-02 14:43:11 -06:00 committed by GitHub
parent 85b8e7c0c8
commit d60f8fed73
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 105 additions and 0 deletions

View File

@ -82,6 +82,43 @@ namespace chaiscript {
return dispatch::detail::make_callable(std::forward<T>(t), dispatch::detail::function_signature(t));
}
/// \brief Creates a new Proxy_Function object from an overloaded free function, with the
/// signature specified explicitly to disambiguate the overload.
///
/// \b Example:
/// \code
/// int overloaded(int);
/// double overloaded(double);
///
/// chai.add(chaiscript::fun<int(int)>(&overloaded), "overloaded");
/// chai.add(chaiscript::fun<double(double)>(&overloaded), "overloaded");
/// \endcode
template<typename Sig, std::enable_if_t<std::is_function_v<Sig>, int> = 0>
Proxy_Function fun(std::type_identity_t<Sig> *f) {
return dispatch::detail::make_callable(f, dispatch::detail::function_signature(f));
}
/// \brief Creates a new Proxy_Function object from an overloaded member function, with the
/// signature specified explicitly to disambiguate the overload.
///
/// \b Example:
/// \code
/// class MyClass {
/// public:
/// int overloaded(int);
/// double overloaded(double);
/// int const_overloaded() const;
/// };
///
/// chai.add(chaiscript::fun<int(int)>(&MyClass::overloaded), "overloaded");
/// chai.add(chaiscript::fun<double(double)>(&MyClass::overloaded), "overloaded");
/// chai.add(chaiscript::fun<int() const>(&MyClass::const_overloaded), "const_overloaded");
/// \endcode
template<typename Sig, typename Class, std::enable_if_t<std::is_function_v<Sig>, int> = 0>
Proxy_Function fun(std::type_identity_t<Sig> Class::*f) {
return dispatch::detail::make_callable(f, dispatch::detail::function_signature(f));
}
/// \brief Creates a new Proxy_Function object from a free function, member function or data member and binds the first parameter of it
/// \param[in] t Function / member to expose
/// \param[in] q Value to bind to first parameter

View File

@ -581,6 +581,74 @@ TEST_CASE("Utility_Test utility class wrapper") {
chai.eval("t = Utility_Test();");
}
///// Issue 612: fun<Sig>(&overloaded) should work for free and member function overloads
namespace issue_612 {
std::string free_overload(int) { return "int"; }
std::string free_overload(double) { return "double"; }
std::string free_overload(const std::string &, bool) { return "string,bool"; }
class Issue_612_Class {
public:
std::string member_overload(int) { return "int"; }
std::string member_overload(double) { return "double"; }
std::string const_member_overload(int) const { return "const_int"; }
std::string const_member_overload(double) const { return "const_double"; }
};
} // namespace issue_612
TEST_CASE("Issue 612: fun<Signature> for overloaded free functions") {
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
chai.add(chaiscript::fun<std::string(int)>(&issue_612::free_overload), "free_overload");
chai.add(chaiscript::fun<std::string(double)>(&issue_612::free_overload), "free_overload");
chai.add(chaiscript::fun<std::string(const std::string &, bool)>(&issue_612::free_overload), "free_overload");
CHECK(chai.eval<std::string>("free_overload(1)") == "int");
CHECK(chai.eval<std::string>("free_overload(1.5)") == "double");
CHECK(chai.eval<std::string>("free_overload(\"hi\", true)") == "string,bool");
}
TEST_CASE("Issue 612: fun<Signature> for overloaded member functions (pointer-to-member form)") {
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
chai.add(chaiscript::user_type<issue_612::Issue_612_Class>(), "Issue_612_Class");
chai.add(chaiscript::constructor<issue_612::Issue_612_Class()>(), "Issue_612_Class");
chai.add(chaiscript::fun<std::string (issue_612::Issue_612_Class::*)(int)>(&issue_612::Issue_612_Class::member_overload),
"member_overload");
chai.add(chaiscript::fun<std::string (issue_612::Issue_612_Class::*)(double)>(&issue_612::Issue_612_Class::member_overload),
"member_overload");
chai.add(chaiscript::fun<std::string (issue_612::Issue_612_Class::*)(int) const>(&issue_612::Issue_612_Class::const_member_overload),
"const_member_overload");
chai.add(chaiscript::fun<std::string (issue_612::Issue_612_Class::*)(double) const>(&issue_612::Issue_612_Class::const_member_overload),
"const_member_overload");
chai.eval("var t = Issue_612_Class();");
CHECK(chai.eval<std::string>("t.member_overload(1)") == "int");
CHECK(chai.eval<std::string>("t.member_overload(1.5)") == "double");
CHECK(chai.eval<std::string>("t.const_member_overload(1)") == "const_int");
CHECK(chai.eval<std::string>("t.const_member_overload(1.5)") == "const_double");
}
TEST_CASE("Issue 612: fun<Signature> for overloaded member functions (function-type form)") {
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
chai.add(chaiscript::user_type<issue_612::Issue_612_Class>(), "Issue_612_Class");
chai.add(chaiscript::constructor<issue_612::Issue_612_Class()>(), "Issue_612_Class");
chai.add(chaiscript::fun<std::string(int)>(&issue_612::Issue_612_Class::member_overload), "member_overload");
chai.add(chaiscript::fun<std::string(double)>(&issue_612::Issue_612_Class::member_overload), "member_overload");
chai.add(chaiscript::fun<std::string(int) const>(&issue_612::Issue_612_Class::const_member_overload), "const_member_overload");
chai.add(chaiscript::fun<std::string(double) const>(&issue_612::Issue_612_Class::const_member_overload), "const_member_overload");
chai.eval("var t = Issue_612_Class();");
CHECK(chai.eval<std::string>("t.member_overload(1)") == "int");
CHECK(chai.eval<std::string>("t.member_overload(1.5)") == "double");
CHECK(chai.eval<std::string>("t.const_member_overload(1)") == "const_int");
CHECK(chai.eval<std::string>("t.const_member_overload(1.5)") == "const_double");
}
enum Utility_Test_Numbers {
ONE,
TWO,