C++泛型Rule类实例化失败求助:支持任意可调用对象
Rule类模板参数推导与执行错误的解决方案
项目背景
开发一个多规则实例系统,每个Rule执行动作后根据Message枚举传递控制权,动作支持任意可调用对象(函数、lambda等),可接受任意参数但必须返回Message。
原始代码
#include <concepts> #include <map> #include <unordered_map> #include <functional> #include <iostream> enum Message { success, failure, nothing }; class BaseRule {}; template <typename F, typename ...Args> requires std::invocable<F, Args...> class Rule: public BaseRule { private: std::map<Message, BaseRule*> successors; //a feature allowing to chain rules, unused at this moment template <typename... Ts> struct undef; // for testing purposes only public: F action; Rule(F _action) : action(_action) {}; void addSuccessor(const Message, BaseRule*); void removeSuccessor(const Message); template <typename... ExecuteArgs> void execute(ExecuteArgs&&... args) { //execute the action, and call the next rule's action according to the Message returned if constexpr (std::is_invocable_v<F, ExecuteArgs...>) std::invoke(action, std::forward<ExecuteArgs>(args)...); else undef<F, ExecuteArgs...> _; // for testing purposes, expected to fail and give me the types of F and ExecuteArgs in the error message }; }; class App { private: std::unordered_map<unsigned int, std::function<void()>> boundActions; public: template <typename F, typename ...Args> requires std::invocable<F, Args...> void bindInput(const unsigned int _key, BaseRule* _rule, Args... _args) { auto callable = [_rule, _args...]() { static_cast<Rule<F, Args...>*>(_rule)->execute(_args...); }; boundActions[_key] = callable; }; template <typename F, typename ...Args> requires std::invocable<F, Args...> void launch(Rule<F> _startingPoint, Args... args) { _startingPoint.execute(args...); loop(); }; void loop() { while (!quit) { for (unsigned int i = 0; i < 256; ++i) { if (downKeys[i]) { auto it = boundActions.find(i); if (it != boundActions.end()) it->second(); // Invoke the stored callable object } } } }; }; int main() { App app = App(); //app is captured in the lamba to perform some logic Rule init = Rule([&app]() -> Message { std::cout << "Initialization" << std::endl; return nothing; }); //This fails Rule mark = Rule([&app](const unsigned int _target) -> Message { std::cout << "Mark" << std::endl; return success; }); app.bindInput<decltype(init.action)>(32, &init); app.bindInput<decltype(mark.action)>(65, &mark, 0); //Thus, this also fails app.bindInput<decltype(mark.action)>(90, &mark, 1); //Same app.launch(&init); return 0; }
遇到的问题
- 带参数Rule实例化失败:无法从带参数的lambda推导
Rule<F>的模板参数,报C2748错误;替换为普通函数或显式指定模板参数时,出现C7602约束不满足错误,仅无参数函数可正常工作。 - execute函数编译错误:
if constexpr (std::is_invocable_v<F, ExecuteArgs...>)处触发C2100非法间接寻址错误。
问题分析与修复方案
1. 重构Rule类模板参数
原始Rule类模板同时指定F和Args,导致编译器无法自动推导Args(lambda的参数类型是其自身类型的一部分,无需额外指定)。修改为仅用F作为模板参数,将可调用约束移至execute函数,并检查返回值是否为Message:
template <typename F> class Rule: public BaseRule { private: std::map<Message, BaseRule*> successors; template <typename... Ts> struct undef; public: F action; Rule(F _action) : action(std::move(_action)) {}; void addSuccessor(const Message msg, BaseRule* rule) { successors[msg] = rule; } void removeSuccessor(const Message msg) { successors.erase(msg); } template <typename... ExecuteArgs> auto execute(ExecuteArgs&&... args) -> Message { if constexpr (std::is_invocable_r_v<Message, F, ExecuteArgs...>) { Message result = std::invoke(action, std::forward<ExecuteArgs>(args)...); // 此处可添加根据result调用后续规则的逻辑 auto it = successors.find(result); if (it != successors.end()) { // 示例:假设后续规则无参数,实际可根据需求调整 static_cast<Rule<F>*>(it->second)->execute(); } return result; } else { undef<F, ExecuteArgs...> _; return nothing; // 仅为编译通过,实际会触发undef的类型错误 } }; };
2. 修正bindInput函数的类型转换
原始bindInput中强制转换Rule<F, Args...>类型错误,改为推导实际的RuleT类型,并用std::apply处理参数转发:
template <typename RuleT, typename ...Args> requires std::is_base_of_v<BaseRule, RuleT> && std::is_invocable_r_v<Message, decltype(std::declval<RuleT>().action), Args...> void bindInput(const unsigned int _key, RuleT* _rule, Args... _args) { auto callable = [_rule, args = std::make_tuple(std::forward<Args>(_args)...)]() { std::apply([_rule](auto&&... inner_args) { _rule->execute(std::forward<decltype(inner_args)>(inner_args)...); }, args); }; boundActions[_key] = std::move(callable); }
3. 修复launch函数参数不匹配
原始launch接受Rule<F>值,但传入的是指针,改为接受RuleT引用,并添加约束确保动作可调用:
template <typename RuleT, typename ...Args> requires std::is_base_of_v<BaseRule, RuleT> && std::is_invocable_r_v<Message, decltype(std::declval<RuleT>().action), Args...> void launch(RuleT& _startingPoint, Args... args) { _startingPoint.execute(std::forward<Args>(args)...); loop(); }
4. 补充App类缺失成员
原始代码中loop函数使用的quit和downKeys未定义,需补充:
class App { private: std::unordered_map<unsigned int, std::function<void()>> boundActions; bool quit = false; bool downKeys[256] = {false}; // ... 其他函数 };
完整修复后代码
#include <concepts> #include <map> #include <unordered_map> #include <functional> #include <iostream> #include <tuple> enum Message { success, failure, nothing }; class BaseRule {}; template <typename F> class Rule: public BaseRule { private: std::map<Message, BaseRule*> successors; template <typename... Ts> struct undef; public: F action; Rule(F _action) : action(std::move(_action)) {}; void addSuccessor(const Message msg, BaseRule* rule) { successors[msg] = rule; } void removeSuccessor(const Message msg) { successors.erase(msg); } template <typename... ExecuteArgs> auto execute(ExecuteArgs&&... args) -> Message { if constexpr (std::is_invocable_r_v<Message, F, ExecuteArgs...>) { Message result = std::invoke(action, std::forward<ExecuteArgs>(args)...); // 根据返回值调用后续规则示例 auto it = successors.find(result); if (it != successors.end()) { // 此处可根据后续规则的参数需求调整execute调用 static_cast<Rule<F>*>(it->second)->execute(); } return result; } else { undef<F, ExecuteArgs...> _; return nothing; } }; }; class App { private: std::unordered_map<unsigned int, std::function<void()>> boundActions; bool quit = false; bool downKeys[256] = {false}; public: template <typename RuleT, typename ...Args> requires std::is_base_of_v<BaseRule, RuleT> && std::is_invocable_r_v<Message, decltype(std::declval<RuleT>().action), Args...> void bindInput(const unsigned int _key, RuleT* _rule, Args... _args) { auto callable = [_rule, args = std::make_tuple(std::forward<Args>(_args)...)]() { std::apply([_rule](auto&&... inner_args) { _rule->execute(std::forward<decltype(inner_args)>(inner_args)...); }, args); }; boundActions[_key] = std::move(callable); } template <typename RuleT, typename ...Args> requires std::is_base_of_v<BaseRule, RuleT> && std::is_invocable_r_v<Message, decltype(std::declval<RuleT>().action), Args...> void launch(RuleT& _startingPoint, Args... args) { _startingPoint.execute(std::forward<Args>(args)...); loop(); } void loop() { while (!quit) { for (unsigned int i = 0; i < 256; ++i) { if (downKeys[i]) { auto it = boundActions.find(i); if (it != boundActions.end()) { it->second(); downKeys[i] = false; // 避免重复触发 } } } } } // 模拟按键按下的辅助函数 void pressKey(unsigned int key) { if (key < 256) { downKeys[key] = true; } } }; int main() { App app; Rule init([&app]() -> Message { std::cout << "Initialization" << std::endl; return nothing; }); Rule mark([&app](const unsigned int _target) -> Message { std::cout << "Mark target: " << _target << std::endl; return success; }); app.bindInput(32, &init); app.bindInput(65, &mark, 0); app.bindInput(90, &mark, 1); // 模拟按键触发 app.pressKey(32); app.pressKey(65); app.pressKey(90); app.launch(init); return 0; }
内容的提问来源于stack exchange,提问作者MetaZenithian
相关产品推荐
相关产品推荐

