如何简化C++20中包装成员函数的std::map实现?
方案一:修改AnyCallable以支持成员函数指针
AnyCallable的核心是通过类型擦除统一不同签名的可调用对象接口。要直接支持成员函数指针,关键是处理成员函数依赖的this指针——我们可以在构造时绑定Pipe实例,把成员函数转换为普通可调用对象。
步骤与代码实现
- 先定义示例Pipe类:
#include <any> #include <vector> #include <map> #include <memory> #include <string> #include <iostream> #include <tuple> #include <stdexcept> #include <utility> class Pipe { public: int Add(int a, int b) { return a + b; } std::string Greet(const std::string& name) { return "Hello, " + name; } void Print(const std::string& msg) { std::cout << msg << std::endl; } };
- 扩展原有AnyCallable,添加成员函数指针的构造重载:
namespace detail { // 辅助函数:解析std::any参数并调用普通函数 template<typename Func, size_t... Idx, typename ArgsTuple> std::any InvokeHelper(Func&& func, std::index_sequence<Idx...>, ArgsTuple&& args) { using RetType = decltype(func(std::get<Idx>(std::forward<ArgsTuple>(args))...)); if constexpr (std::is_void_v<RetType>) { func(std::get<Idx>(std::forward<ArgsTuple>(args))...); return std::any(); } else { return std::any(func(std::get<Idx>(std::forward<ArgsTuple>(args))...)); } } template<typename Func, typename... Args> std::any InvokeFunc(Func&& func, std::vector<std::any> args) { if (args.size() != sizeof...(Args)) { throw std::invalid_argument("Argument count mismatch"); } // 将std::vector<std::any>转换为对应类型的tuple std::tuple<std::decay_t<Args>...> args_tuple; std::apply([&](auto&... elem) { size_t idx = 0; ((elem = std::any_cast<std::decay_t<Args>>(args[idx++])), ...); }, args_tuple); return InvokeHelper(std::forward<Func>(func), std::index_sequence_for<Args...>{}, std::move(args_tuple)); } } class AnyCallable { public: // 原有构造函数:支持普通可调用对象 template<typename Func> AnyCallable(Func&& func) : impl_(std::make_unique<Impl<Func>>(std::forward<Func>(func))) {} // 新增构造函数:直接接受Pipe成员函数指针和实例 template<typename Ret, typename... Args> AnyCallable(Ret (Pipe::*mem_func)(Args...), Pipe* instance) : impl_(std::make_unique<Impl<std::function<Ret(Args...)>>>( [mem_func, instance](Args... args) { return (instance->*mem_func)(std::forward<Args>(args)...); } )) {} // 统一调用接口 std::any operator()(std::vector<std::any> args) { return impl_->Invoke(std::move(args)); } private: struct BaseImpl { virtual ~BaseImpl() = default; virtual std::any Invoke(std::vector<std::any> args) = 0; }; template<typename Func> struct Impl : BaseImpl { Func func_; Impl(Func&& func) : func_(std::forward<Func>(func)) {} std::any Invoke(std::vector<std::any> args) override { // 利用模板推导获取函数的参数类型 return detail::InvokeFunc(func_, std::move(args)); } }; std::unique_ptr<BaseImpl> impl_; };
- 使用方式(直接传入成员函数指针):
int main() { Pipe pipe; std::map<std::string, AnyCallable> method_map; // 直接绑定成员函数指针和Pipe实例 method_map["Add"] = AnyCallable(&Pipe::Add, &pipe); method_map["Greet"] = AnyCallable(&Pipe::Greet, &pipe); method_map["Print"] = AnyCallable(&Pipe::Print, &pipe); // 调用示例 auto add_result = method_map["Add"]({1, 2}); std::cout << std::any_cast<int>(add_result) << std::endl; auto greet_result = method_map["Greet"]({"Alice"}); std::cout << std::any_cast<std::string>(greet_result) << std::endl; method_map["Print"]({"Hello RPC!"}); return 0; }
方案二:创建PipeMethodWrapper类包装成员函数
这种方案不需要修改原有AnyCallable,而是新增一个专门针对Pipe成员函数的包装类,通过类型擦除封装成员函数指针和Pipe实例,提供统一的调用接口。
步骤与代码实现
- 定义PipeMethodWrapper类:
namespace detail { // 辅助函数:解析参数并调用Pipe成员函数 template<typename Ret, typename... Args, size_t... Idx> std::any InvokeMemberHelper(Ret (Pipe::*mem_func)(Args...), Pipe* instance, std::index_sequence<Idx...>, std::tuple<Args...>&& args) { if constexpr (std::is_void_v<Ret>) { (instance->*mem_func)(std::get<Idx>(std::move(args))...); return std::any(); } else { return std::any((instance->*mem_func)(std::get<Idx>(std::move(args))...)); } } template<typename Ret, typename... Args> std::any InvokeMemberFunc(Ret (Pipe::*mem_func)(Args...), Pipe* instance, std::tuple<Args...>&& args) { return InvokeMemberHelper(mem_func, instance, std::index_sequence_for<Args...>{}, std::move(args)); } } class PipeMethodWrapper { public: // 构造函数:接受Pipe成员函数指针和实例 template<typename Ret, typename... Args> PipeMethodWrapper(Ret (Pipe::*mem_func)(Args...), Pipe* instance) : impl_(std::make_unique<Impl<Ret, Args...>>(mem_func, instance)) {} // 统一调用接口 std::any operator()(std::vector<std::any> args) { return impl_->Invoke(std::move(args)); } private: struct BaseImpl { virtual ~BaseImpl() = default; virtual std::any Invoke(std::vector<std::any> args) = 0; }; // 针对不同成员函数签名的具体实现 template<typename Ret, typename... Args> struct Impl : BaseImpl { Ret (Pipe::*mem_func_)(Args...); Pipe* instance_; Impl(Ret (Pipe::*mem_func)(Args...), Pipe* instance) : mem_func_(mem_func), instance_(instance) {} std::any Invoke(std::vector<std::any> args) override { if (args.size() != sizeof...(Args)) { throw std::invalid_argument("Argument count mismatch"); } // 转换参数为对应类型的tuple std::tuple<std::decay_t<Args>...> args_tuple; std::apply([&](auto&... elem) { size_t idx = 0; ((elem = std::any_cast<std::decay_t<Args>>(args[idx++])), ...); }, args_tuple); return detail::InvokeMemberFunc(mem_func_, instance_, std::move(args_tuple)); } }; std::unique_ptr<BaseImpl> impl_; };
- 使用方式:
int main() { Pipe pipe; std::map<std::string, PipeMethodWrapper> method_map; // 直接用成员函数指针构造包装类 method_map["Add"] = PipeMethodWrapper(&Pipe::Add, &pipe); method_map["Greet"] = PipeMethodWrapper(&Pipe::Greet, &pipe); method_map["Print"] = PipeMethodWrapper(&Pipe::Print, &pipe); // 调用示例 auto add_result = method_map["Add"]({3, 4}); std::cout << std::any_cast<int>(add_result) << std::endl; auto greet_result = method_map["Greet"]({"Bob"}); std::cout << std::any_cast<std::string>(greet_result) << std::endl; method_map["Print"]({"PipeMethodWrapper works!"}); return 0; }
两种方案对比
- 方案一:复用原有AnyCallable的类型擦除逻辑,代码更简洁,但会让AnyCallable的职责范围扩大,从包装任意可调用对象变为同时处理成员函数绑定。
- 方案二:职责单一,专门处理Pipe成员函数的包装,对原有代码无侵入,但需要维护额外的类结构。
内容的提问来源于stack exchange,提问作者Yakumo Fujii
相关产品推荐
相关产品推荐

