如何让C++模板自动选择正确的重载成员函数?
你的核心问题是:当成员函数存在重载时,编译器无法自动为setter_fn模板选择符合要求的重载版本——这是因为模板参数推导的优先级高于约束检查,编译器在确定memfun的具体类型之前,不会去检查哪个重载符合requires条件。
下面给出几种无需手动static_cast的解决方案:
方案1:用requires约束提前筛选重载(C++20+)
修改你的模板,给getter_fn和setter_fn添加更明确的requires条件,让编译器在选择重载时就能排除不符合的候选:
首先补充一个辅助模板来提取成员函数的参数:
#include <type_traits> #include <tuple> // 提取成员函数的参数列表 template<typename T> struct member_function_args; template<typename Klass, typename Ret, typename... Args> struct member_function_args<Ret (Klass::*)(Args...)> { using types = std::tuple<Args...>; };
然后重写getter_fn和setter_fn:
// Getter:匹配无参、非void返回的成员函数 template <auto memfun> requires std::is_member_function_pointer_v<decltype(memfun)> && std::tuple_size_v<typename member_function_args<decltype(memfun)>::types> == 0 && !std::is_void_v<std::invoke_result_t<decltype(memfun), typename member_pointer_class<decltype(memfun)>::type*>> Obj *getter_fn(const vector<FunArg> &args) { using T = member_pointer_class<decltype(memfun)>::type; T *obj = static_cast<T*>(args[0].val); auto res = (obj->*memfun)(); return res; } // Setter:匹配单参数、void返回的成员函数 template <auto memfun> requires std::is_member_function_pointer_v<decltype(memfun)> && std::tuple_size_v<typename member_function_args<decltype(memfun)>::types> == 1 && std::is_void_v<std::invoke_result_t<decltype(memfun), typename member_pointer_class<decltype(memfun)>::type*, typename std::tuple_element_t<0, typename member_function_args<decltype(memfun)>::types>>> Obj *setter_fn(const vector<FunArg> &args) { using T = member_pointer_class<decltype(memfun)>::type; using A0 = typename std::tuple_element_t<0, typename member_function_args<decltype(memfun)>::types>; T *obj = static_cast<T*>(args[0].val); A0 a0 = static_cast<A0>(args[1].val); (obj->*memfun)(a0); return nullptr; }
现在直接调用setter_fn<&Device::foo>时,编译器会自动排除不符合条件的Foo*(Device::*)()重载,只保留void(Device::*)(Foo*)版本。
方案2:用C++20概念明确约束签名
定义专门的概念来匹配getter和setter的成员函数类型,让编译器在推导阶段就筛选出正确的重载:
#include <type_traits> #include <tuple> // ... 保留member_pointer_class和member_function_args模板 // 匹配无参、非void返回的getter成员函数 template <typename Fn> concept GetMemberFn = std::is_member_function_pointer_v<Fn> && []<typename Klass, typename Ret>(Ret (Klass::*)())->bool { return true; }(Fn{}) && !std::is_void_v<std::invoke_result_t<Fn, Klass*>>; // 匹配单参数、void返回的setter成员函数 template <typename Fn> concept SetMemberFn = std::is_member_function_pointer_v<Fn> && []<typename Klass, typename A0>(void (Klass::*)(A0))->bool { return true; }(Fn{}) && std::is_void_v<std::invoke_result_t<Fn, Klass*, A0>>; // Getter模板 template <GetMemberFn auto memfun> Obj *getter_fn(const vector<FunArg> &args) { using T = member_pointer_class<decltype(memfun)>::type; T *obj = static_cast<T*>(args[0].val); auto res = (obj->*memfun)(); return res; } // Setter模板 template <SetMemberFn auto memfun> Obj *setter_fn(const vector<FunArg> &args) { using T = member_pointer_class<decltype(memfun)>::type; using A0 = std::tuple_element_t<0, typename member_function_args<decltype(memfun)>::types>; T *obj = static_cast<T*>(args[0].val); A0 a0 = static_cast<A0>(args[1].val); (obj->*memfun)(a0); return nullptr; }
概念中的lambda模式匹配会直接检查成员函数的签名,编译器在处理&Device::foo时会自动挑选符合SetMemberFn的重载。
方案3:辅助模板自动选择重载
如果不想用C++20的特性,可以写一个辅助模板来帮我们挑选正确的重载:
// 匹配无参getter template <typename Klass, typename Ret> constexpr auto select_member(Ret (Klass::*memfun)()) { return memfun; } // 匹配单参数setter template <typename Klass, typename A0> constexpr auto select_member(void (Klass::*memfun)(A0)) { return memfun; } // 禁用其他不符合的重载 template <typename T> constexpr auto select_member(T) = delete;
调用时通过select_member自动筛选:
make_prop("foo", getter_fn<select_member(&Device::foo)>, setter_fn<select_member(&Device::foo)>);
为什么原来的requires没生效?
你之前的requires约束是在模板参数memfun的类型确定后才会检查的,但编译器在处理&Device::foo时,首先需要确定这个指针指向哪个重载——此时有两个候选,编译器无法自动选择,所以直接报错,根本不会进入约束检查阶段。只有让编译器在选择重载时就能筛选出符合要求的候选,约束才能发挥作用。
内容的提问来源于stack exchange,提问作者Rob N

