如何用单个C++14模板函数兼容const与非const成员函数指针?
用单个C++14模板兼容const/非const成员函数指针
当然可以!你完全不用写两个重载模板,在C++14里我们可以通过将成员函数指针作为整体模板参数推导的方式,用一个模板搞定两种情况,还能为你后续从缓冲区提取参数的需求留足扩展空间。
核心思路:通用模板参数推导
原来的两个重载之所以必要,是因为Ret (Class::*)(Us...)(非const成员函数指针)和Ret (Class::*)(Us...) const(const成员函数指针)是两种完全不同的类型,模板无法自动匹配。但如果我们把成员函数指针作为一个整体的模板参数MethodPtr,让编译器自动推导它的具体类型,就能同时兼容这两种情况。
实现代码如下:
#include <utility> // 用于std::forward template<typename MethodPtr, typename Class, typename... Args> auto callMethod(Class& object, MethodPtr method, Args&&... args) { // 完美转发参数,保证参数传递的正确性 return (object.*method)(std::forward<Args>(args)...); }
测试验证
我们用一个简单的测试类验证这个模板的效果:
#include <iostream> struct Test { void printNonConst() { std::cout << "调用非const成员函数\n"; } void printConst() const { std::cout << "调用const成员函数\n"; } int addConst(int a, int b) const { return a + b; } }; int main() { Test t; const Test ct; // 调用非const成员函数 callMethod(t, &Test::printNonConst); // 调用const成员函数(非const对象可以调用const成员) callMethod(t, &Test::printConst); // const对象调用const成员函数 auto sum = callMethod(ct, &Test::addConst, 3, 5); std::cout << "const成员函数返回值:" << sum << "\n"; // 编译错误:const对象无法调用非const成员函数,符合预期 // callMethod(ct, &Test::printNonConst); }
运行这段代码会输出:
调用非const成员函数 调用const成员函数 const成员函数返回值:8
适配后续需求:从缓冲区提取参数
你提到最终要从数据缓冲区提取方法参数,这个模板已经为扩展做好了准备。只需要调整模板,加入缓冲区参数,并在函数内部解析参数即可。
比如,我们可以借助模板元编程获取成员函数的参数类型,然后从缓冲区解析出对应的值:
// 先实现一个获取成员函数参数类型的元工具(C++14版本) template<typename T> struct member_function_traits; template<typename Ret, typename Class, typename... Args> struct member_function_traits<Ret (Class::*)(Args...)> { using return_type = Ret; using args_tuple = std::tuple<Args...>; }; template<typename Ret, typename Class, typename... Args> struct member_function_traits<Ret (Class::*)(Args...) const> { using return_type = Ret; using args_tuple = std::tuple<Args...>; }; // 调整后的callMethod,支持从缓冲区提取参数 template<typename MethodPtr, typename Class, typename Buffer> auto callMethod(Class& object, MethodPtr method, Buffer& buffer) { // 获取成员函数的参数类型元组 using ArgsTuple = typename member_function_traits<MethodPtr>::args_tuple; // 模拟从缓冲区解析参数(这里需要你根据实际缓冲区格式实现parse_buffer) ArgsTuple args = parse_buffer(buffer); // C++14没有std::apply,我们可以自己实现一个简单版本 return apply_tuple([&object, method](auto&&... parsed_args) { return (object.*method)(std::forward<decltype(parsed_args)>(parsed_args)...); }, args); } // 简单的std::apply替代实现(C++14兼容) template<typename F, typename Tuple, std::size_t... I> auto apply_tuple_impl(F&& f, Tuple&& t, std::index_sequence<I...>) { return std::forward<F>(f)(std::get<I>(std::forward<Tuple>(t))...); } template<typename F, typename Tuple> auto apply_tuple(F&& f, Tuple&& t) { using Indices = std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>; return apply_tuple_impl(std::forward<F>(f), std::forward<Tuple>(t), Indices{}); }
这个版本的callMethod可以接收缓冲区,自动解析出成员函数所需的参数并调用,完全符合你的通用需求。
总结
这种方式不仅避免了代码重复,还保持了模板的通用性:
- 自动兼容const和非const成员函数指针
- 支持任意参数列表的成员函数
- 完美转发参数,保证效率和正确性
- 易于扩展到从缓冲区提取参数的场景
内容的提问来源于stack exchange,提问作者flamasterrr
相关产品推荐
相关产品推荐

