如何用std::any存储可调用对象?解决std::any_cast失败问题
问题根源
原代码的核心问题有两个:
std::any存储的是lambda闭包、std::bind返回值这类原始可调用类型,而非std::function,导致std::any_cast<std::function<void(T...)>>因类型不匹配直接失败。bar模板的参数T...是调用时传入的参数类型,和注册回调的参数类型完全脱节,既无法做编译期校验,也无法利用隐式类型转换。
修正方案
根据你需要的「参数不匹配触发编译错误」核心需求,推荐两种不同场景的方案:
方案一:Test改为模板类(编译期校验参数,推荐)
将Test类改为模板类,用模板参数固定回调的参数类型,编译期就能校验bar的传入参数是否匹配,完全符合需求,且代码简洁高效。
#include <functional> #include <iostream> #include <string> template<typename... Args> class Test { private: std::function<void(Args...)> cb_; public: // 自动适配任何可调用对象(lambda、std::function、std::bind返回值) template <typename Callable> void foo(Callable&& cb) { cb_ = std::forward<Callable>(cb); } // 触发回调,参数类型必须与模板参数匹配,否则直接编译报错 void bar(Args... args) { if (cb_) { std::invoke(cb_, std::forward<Args>(args)...); } } }; void test(int a) { std::cout << a << std::endl; } int main() { Test<std::string> t1; t1.foo([](std::string s) { std::cout << s << std::endl; }); t1.bar("hello"); // 编译通过,const char*隐式转换为std::string // t1.bar(123); // 编译错误:参数类型不匹配 Test<int> t2; t2.foo(std::bind(test, std::placeholders::_1)); t2.bar(2); // 编译通过 // t2.bar("test"); // 编译错误:参数类型不匹配 }
方案二:保持Test非模板类(运行时校验参数)
如果必须保持Test为非模板类,可通过类型擦除实现,但只能在运行时校验参数匹配,无法触发编译错误。
#include <any> #include <functional> #include <iostream> #include <string> #include <tuple> #include <typeinfo> #include <memory> // 辅助工具:推导可调用对象的参数类型 template<typename T> struct FuncTraits; template<typename R, typename... Args> struct FuncTraits<std::function<R(Args...)>> { using ArgsTuple = std::tuple<Args...>; using FuncType = std::function<R(Args...)>; }; template<typename R, typename... Args> struct FuncTraits<R(*)(Args...)> { using ArgsTuple = std::tuple<Args...>; using FuncType = std::function<R(Args...)>; }; template<typename T> struct FuncTraits : FuncTraits<decltype(&T::operator())> {}; template<typename C, typename R, typename... Args> struct FuncTraits<R(C::*)(Args...) const> { using ArgsTuple = std::tuple<Args...>; using FuncType = std::function<R(Args...)>; }; class Test { private: struct CallbackBase { virtual ~CallbackBase() = default; virtual bool match_args(const std::type_info& ti) const = 0; virtual void invoke(void* args) const = 0; }; template<typename... Args> struct CallbackImpl : CallbackBase { std::function<void(Args...)> cb; CallbackImpl(std::function<void(Args...)> func) : cb(std::move(func)) {} bool match_args(const std::type_info& ti) const override { return ti == typeid(std::tuple<Args...>); } void invoke(void* args) const override { auto& args_tuple = *static_cast<std::tuple<Args...>*>(args); std::apply(cb, args_tuple); } }; std::unique_ptr<CallbackBase> cb_; public: template <typename Callable> void foo(Callable&& cb) { using Traits = FuncTraits<Callable>; using FuncType = typename Traits::FuncType; using Args = typename Traits::ArgsTuple; cb_ = std::make_unique<CallbackImpl<std::tuple_element_t<0, Args>, std::tuple_element_t<1, Args>...>>( FuncType(std::forward<Callable>(cb)) ); } template <typename... Args> void bar(Args&&... args) { using ArgsTuple = std::tuple<std::decay_t<Args>...>; if (!cb_) { std::cerr << "未注册回调" << std::endl; return; } if (!cb_->match_args(typeid(ArgsTuple))) { std::cerr << "参数类型不匹配" << std::endl; return; } auto args_tuple = std::make_tuple(std::forward<Args>(args)...); cb_->invoke(&args_tuple); } }; void test(int a) { std::cout << a << std::endl; } int main() { Test t1; t1.foo([](std::string s) { std::cout << s << std::endl; }); t1.bar("hello"); // 正常输出hello t1.bar(123); // 运行时提示参数不匹配 Test t2; t2.foo(std::bind(test, std::placeholders::_1)); t2.bar(2); // 正常输出2 t2.bar("test"); // 运行时提示参数不匹配 }
内容的提问来源于stack exchange,提问作者john
相关产品推荐
相关产品推荐

