编译期检查字符串引号:JsonKey构造函数static_assert报错疑问
编译期断言JsonKey字符串不含引号失败的原因与解决方法
我希望断言当使用编译期已知的字符串构造JsonKey对象时,该字符串不包含引号,编写了如下代码:
#include<iostream> template <size_t N> constexpr bool check_no_quotes(const char (&str)[N]) { for (size_t i = 0; i < N; i++) // Initialize 'i' here { if (str[i] == '"') // No need for the backslash here return false; } return true; } struct JsonKey { template <size_t N> constexpr JsonKey(const char (&str)[N]) { static_assert(check_no_quotes(str), "Can't have quotes"); } }; int main() { // This will compile because it does not contain quotes JsonKey("json_key_without_quotes"); // This will fail to compile because it contains quotes // JsonKey("json key with \"quotes\""); // Uncommenting this will trigger the static_assert error return 0; }
编译时出现如下错误:
<source>: In instantiation of 'constexpr JsonKey::JsonKey(const char (&)[N]) [with long unsigned int N = 24]': <source>:26:38: required from here 26 | JsonKey("json_key_without_quotes"); | ^ <source>:19:38: error: non-constant condition for static assertion 19 | static_assert(check_no_quotes(str), "Can't have quotes"); | ~~~~~~~~~~~~~~~^~~~~ <source>:19:38: in 'constexpr' expansion of 'check_no_quotes<24>((* & str))' <source>:8:16: error: 'str' is not a constant expression 8 | if (str[i] == '"') // No need for the backslash here | ~~~^ Compiler returned: 1
我无法理解为什么此处的str在编译期不可用。
错误原因
static_assert的条件必须是编译期可求值的常量表达式,但你传递给check_no_quotes的str是构造函数的参数——哪怕它绑定的是字符串字面量,在函数参数的语境下,str不属于常量表达式上下文。编译器无法保证这个参数始终是编译期已知的数组(理论上它可以接收运行时生成的数组),因此无法在编译期解析str[i]的值。
解决方法
要让编译器确认传入的是编译期字符串字面量,必须把字符串信息嵌入到模板参数中,让编译器在模板实例化阶段就能获取完整的编译期信息。以下是两种可行方案:
方案1:C++20 基于std::string_view的非类型模板参数(推荐)
C++20支持将std::string_view作为非类型模板参数,直接传递字符串字面量:
#include <string_view> constexpr bool check_no_quotes(std::string_view str) { for (char c : str) { if (c == '"') return false; } return true; } struct JsonKey { template <std::string_view Str> constexpr JsonKey() { static_assert(check_no_quotes(Str), "JsonKey字符串不能包含引号"); } }; int main() { // 编译通过:无引号 JsonKey<"json_key_without_quotes">(); // 编译失败:包含引号 // JsonKey<"json key with \"quotes\"">(); }
方案2:C++17及更早版本的模板数组参数
如果无法使用C++20,可以将字符串数组作为模板非类型参数传递:
template <size_t N> constexpr bool check_no_quotes(const char (&str)[N]) { for (size_t i = 0; i < N - 1; ++i) // 跳过末尾的'\0',无需检查 { if (str[i] == '"') return false; } return true; } struct JsonKey { template <size_t N, const char (&Str)[N]> constexpr JsonKey() { static_assert(check_no_quotes(Str), "JsonKey字符串不能包含引号"); } }; // 定义编译期常量字符串作为模板参数载体 constexpr auto valid_key = "json_key_without_quotes"; constexpr auto invalid_key = "json key with \"quotes\""; int main() { // 编译通过 JsonKey<sizeof(valid_key), valid_key>(); // 编译失败 // JsonKey<sizeof(invalid_key), invalid_key>(); }
补充说明
- 原代码中
check_no_quotes遍历到N(包含字符串末尾的\0)是多余的,因为\0不可能是引号,改为遍历到N-1可以减少不必要的检查。 - 模板参数传递字符串的核心是让编译器在实例化阶段就拿到完整的编译期字符串信息,而非通过函数参数间接传递,这样才能满足
static_assert对常量表达式的要求。
内容的提问来源于stack exchange,提问作者glades
相关产品推荐
相关产品推荐

