如何用C++ Concepts推断函数参数类型并校验允许类型
解决方案
要实现对命令处理器参数类型的校验,你可以通过类型萃取(Type Traits)提取可调用对象的参数类型,再结合编译期断言/Concept逐一验证每个参数是否属于允许的类型集合。以下是具体实现步骤:
1. 定义单个类型的合法性检查Trait
先写一个编译期Trait,判断单个类型是否在允许的列表(double/int/std::string/bool)中:
#include <type_traits> #include <string> #include <tuple> // 判断单个类型是否为允许的命令参数类型 template <typename T> constexpr bool is_allowed_type_v = std::disjunction_v< std::is_same<T, double>, std::is_same<T, int>, std::is_same<T, std::string>, std::is_same<T, bool> >;
2. 定义参数包的全合法性检查Trait
接着实现一个Trait,检查参数包中的所有类型都满足上述单个类型的合法性:
// 判断参数包中所有类型是否均为允许类型 template <typename... Args> constexpr bool all_allowed_types_v = (is_allowed_type_v<Args> && ...);
3. 萃取可调用对象的参数类型
为了提取函数、Lambda、成员函数等可调用对象的参数类型,我们可以实现一个function_traits工具:
// 基础模板:匹配普通函数签名 template <typename R, typename... Args> struct function_traits { using return_type = R; using args_tuple = std::tuple<Args...>; static constexpr size_t arity = sizeof...(Args); // 获取第N个参数的类型 template <size_t N> using arg_type = std::tuple_element_t<N, args_tuple>; }; // 特化:匹配函数指针 template <typename R, typename... Args> struct function_traits<R(*)(Args...)> : function_traits<R(Args...)> {}; // 特化:匹配非const成员函数指针 template <typename C, typename R, typename... Args> struct function_traits<R(C::*)(Args...)> : function_traits<R(Args...)> {}; // 特化:匹配const成员函数指针 template <typename C, typename R, typename... Args> struct function_traits<R(C::*)(Args...) const> : function_traits<R(Args...)> {}; // 特化:匹配Lambda和其他带operator()的可调用对象 template <typename F> struct function_traits : function_traits<decltype(&F::operator())> {}; // 特化:匹配无参的const成员函数(Lambda常用) template <typename C, typename R> struct function_traits<R(C::*)() const> : function_traits<R()> {};
4. 整合到Concept中进行约束
最后,把参数类型校验和已有的可调用性检查整合到Concept里,作为addCommand的模板约束:
// 定义合法命令处理器的Concept template <typename F> concept ValidCommandHandler = std::invocable<F> && // 确保对象可调用(你已有的检查) []<typename... Args>(std::type_identity_t<Args...>) { return all_allowed_types_v<Args...>; }(typename function_traits<F>::args_tuple{}); // 受约束的addCommand函数 template <ValidCommandHandler F> void addCommand(std::string name, F handler) { // 这里添加命令注册逻辑 }
测试示例
以下是几个测试用例,验证约束是否生效:
// 合法:参数都是允许类型 void test_cmd1(int a, bool b, std::string c) {} // 非法:参数包含不允许的float void test_cmd2(float a) {} int main() { addCommand("test1", test_cmd1); // 编译通过 // addCommand("test2", test_cmd2); // 编译报错:触发参数类型校验失败 addCommand("test3", [](double d) { return; }); // Lambda合法,编译通过 return 0; }
关键说明
function_traits通过模板特化覆盖了几乎所有可调用对象类型(普通函数、函数指针、成员函数、Lambda),能准确提取参数类型。all_allowed_types_v利用C++17的折叠表达式,简洁地实现了参数包的全量检查。- Concept将可调用性和参数合法性检查整合,在编译期就会拦截非法的命令处理器,不需要运行时检查。
内容的提问来源于stack exchange,提问作者ChronosXYZ
相关产品推荐
相关产品推荐

