如何在C++编译时检测const char*格式错误?实现SQL占位符校验
C++20(MSVC)实现编译时SQL占位符校验方案
问题1:编译时统计字符数量并触发断言
原代码的核心问题是constexpr变量不可修改,且字符串字面量的计数逻辑需要完全在编译时上下文执行。修正方案是将计数逻辑抽离为独立的constexpr函数:
#include <cstddef> constexpr size_t count_char(const char* str, size_t size, char target) { size_t cnt = 0; for (size_t i = 0; i < size; ++i) { if (str[i] == target) cnt++; } return cnt; } template <size_t Size> void my_func(const char(&str)[Size]) { constexpr size_t a_count = count_char(str, Size, 'a'); static_assert(a_count == 2, "字符串中'a'的数量必须为2"); // 后续业务逻辑 } int main() { my_func("abc abc"); // 编译错误:a_count为1,不符合断言要求 // my_func("aab abc"); // 编译通过 }
问题2:解决SQL占位符与参数数量的重载匹配问题
原counting_string的模板参数设计导致编译器无法从sizeof...(Args)反向推导字符串对应的占位符数量,调整结构让计数结果成为结构体的静态编译时常量,再通过推导指引自动匹配模板参数:
#include <array> #include <cstddef> template <char TargetChar, size_t Size> struct counting_string { std::array<char, Size> data; // 编译时计算目标字符数量 static constexpr size_t count = []() { size_t cnt = 0; for (size_t i = 0; i < Size; ++i) { if (data[i] == TargetChar) cnt++; } return cnt; }(); // consteval确保构造逻辑在编译时执行 consteval counting_string(const char(&str)[Size]) : data{} { for (size_t i = 0; i < Size; ++i) { data[i] = str[i]; } } }; // 推导指引:让编译器自动推导Size参数 template <char TargetChar, size_t Size> counting_string(const char(&)[Size]) -> counting_string<TargetChar, Size>; // prepare函数:编译时校验占位符与参数数量一致 template <size_t Size, typename... Args> void prepare(counting_string<'?', Size> sql, Args&&... args) { static_assert(sql.count == sizeof...(Args), "占位符数量与参数数量不匹配"); // 使用sql.data()获取字符串内容执行预处理 // ... } int main() { prepare("insert into test (name, age) values (?, ?)", 1, "Alice"); // 编译通过 // prepare("insert into test (name) values (?)", 1, "Bob"); // 编译错误:数量不匹配 }
最终实现:符合需求的SQL编译时校验与绑定
针对你期望的代码结构,调整constexpr_counting_string的存储与推导逻辑,同时完善参数绑定的编译时类型校验:
#include <array> #include <cstddef> #include <type_traits> #include <utility> // 模拟业务类型,请根据实际项目替换 enum class code_t { ok, error }; struct statement_t { code_t bind(size_t idx, uint64_t val) { return code_t::ok; } code_t bind(size_t idx, int64_t val) { return code_t::ok; } code_t bind(size_t idx, int val) { return code_t::ok; } code_t bind(size_t idx, const char* val) { return code_t::ok; } }; code_t helper_prepare(statement_t& stmt, const auto& sql) { // 模拟SQL预处理逻辑 return code_t::ok; } template <char TargetChar, size_t Size> struct constexpr_counting_string { std::array<char, Size> m_chars; // 编译时计算目标字符(占位符)的数量 static constexpr size_t target_count = []() { size_t cnt = 0; for (size_t i = 0; i < Size; ++i) { if (m_chars[i] == TargetChar) cnt++; } return cnt; }(); consteval constexpr_counting_string(const char(&str)[Size]) : m_chars{} { for (size_t i = 0; i < Size; ++i) { m_chars[i] = str[i]; } } const char* data() const noexcept { return m_chars.data(); } }; // 推导指引:自动推导字符串长度参数 template <char TargetChar, size_t Size> constexpr_counting_string(const char(&)[Size]) -> constexpr_counting_string<TargetChar, Size>; // 核心prepare函数:编译时校验占位符数量+参数绑定 template <typename... Args> code_t prepare(statement_t& stmt, constexpr_counting_string<'?', auto> sql, Args&&... args) noexcept { static_assert(sql.target_count == sizeof...(Args), "SQL占位符数量与传入参数数量不匹配"); code_t error = helper_prepare(stmt, sql); size_t index = 1; // SQL占位符通常从1开始计数,可按需调整 auto do_bind = [&](auto&& arg) noexcept { if (error != code_t::ok) return; using ArgType = std::decay_t<decltype(arg)>; if constexpr (std::is_same_v<ArgType, uint64_t>) { error = stmt.bind(index, arg); } else if constexpr (std::is_same_v<ArgType, int64_t>) { error = stmt.bind(index, arg); } else if constexpr (std::is_same_v<ArgType, int>) { error = stmt.bind(index, arg); } else if constexpr (std::is_same_v<ArgType, const char*>) { error = stmt.bind(index, arg); } else { static_assert(std::is_void_v<ArgType>, "不支持的参数类型"); } index++; }; (do_bind(std::forward<Args>(args)), ...); return error; } // 适配无statement参数的调用场景 template <typename... Args> code_t prepare(constexpr_counting_string<'?', auto> sql, Args&&... args) noexcept { statement_t dummy_stmt; return prepare(dummy_stmt, sql, std::forward<Args>(args)...); } int main() { // 编译通过:2个占位符对应2个参数 prepare("insert into test (name, age) values (?, ?)", 25, "Alice"); // 编译错误:占位符数量与参数数量不匹配 // prepare("insert into test (name) values (?)", 25, "Alice"); // 编译错误:参数类型不支持 // prepare("insert into test (age) values (?)", 3.14f); return 0; }
关键优化点
- 编译时计数:通过静态constexpr成员
target_count在编译时完成占位符统计,无运行时开销 - 自动推导:利用C++20的推导指引,让编译器自动从字符串字面量推导模板参数
- 严格校验:通过
static_assert在编译时拦截占位符数量不匹配、参数类型不支持的情况 - 零开销绑定:使用折叠表达式和constexpr分支完成参数绑定,编译时生成最优代码
内容的提问来源于stack exchange,提问作者microlife
相关产品推荐
相关产品推荐

