C++20及以下无宏实现超长字符串编译错误触发方案问询
嵌入式固定长度字符串的编译时长度校验实现
需求说明
需要实现适用于嵌入式场景的fixed_str固定长度字符串结构体,满足:
- 运行时赋值超长字符串时自动静默截断(因显示设备尺寸限制,最终内容都会被裁剪)
- 编译期通过以下方式赋值超长字符串时,触发编译错误:
fixed_str<5> str = "bla bla"; // 超长,需编译报错 fixed_str<5> str2 {"bla bla"}; // 超长,需编译报错 fixed_str<5> str3 = std::string_view{"bla bla"}; // 超长,需编译报错 fixed_str<5> str3 {std::string_view{"bla bla"}}; // 超长,需编译报错
现有实现问题
尝试的实现中,在operator=内通过std::is_constant_evaluated()判断编译期,再用static_assert校验长度,但函数参数无法作为static_assert的常量表达式参数,即使标记consteval重载也无效。同时不想使用宏(无法被命名空间包裹,易引发命名冲突)。
原有实现代码:
template<size_t length> struct fixed_str final { constexpr static auto max_length = length; static constexpr const char zero_char = 0; constexpr fixed_str() { chars[0] = 0; } constexpr fixed_str(const char* str) { this->operator=(str); } fixed_str& operator=(const char* str) { if (str) { auto len = std::char_traits<char>::length(str); auto real_len = std::min(len, length); if (std::is_constant_evaluated()) { static_assert(len <= length, "Cannot fit the string into static buffer"); for (size_t i = 0; i < real_len; ++i) { chars[i] = str[i]; } } else { memcpy(chars.data(), str, real_len); } chars[real_len] = 0; } else { chars[0] = 0; } return *this; } std::array<char, length + 1> chars; constexpr operator std::string_view() const { return { chars.data() }; } };
解决方案
核心思路是区分编译期常量输入(字符串字面量、编译期已知的std::string_view)和运行时输入,针对不同场景实现重载:
1. 字符串字面量处理:模板构造/赋值
利用字符串字面量可推导模板参数的特性,直接在编译期获取长度并校验:
template<size_t length> struct fixed_str final { constexpr static auto max_length = length; std::array<char, length + 1> chars{}; // 默认构造 constexpr fixed_str() noexcept { chars[0] = '\0'; } // 字符串字面量构造:模板参数N为字面量总长度(含终止符) template<size_t N> constexpr fixed_str(const char (&str)[N]) noexcept { static_assert(N - 1 <= length, "String exceeds fixed_str capacity"); const auto copy_len = std::min(N - 1, length); for (size_t i = 0; i < copy_len; ++i) { chars[i] = str[i]; } chars[copy_len] = '\0'; } // 字符串字面量赋值重载 template<size_t N> constexpr fixed_str& operator=(const char (&str)[N]) noexcept { static_assert(N - 1 <= length, "String exceeds fixed_str capacity"); const auto copy_len = std::min(N - 1, length); for (size_t i = 0; i < copy_len; ++i) { chars[i] = str[i]; } chars[copy_len] = '\0'; return *this; }
2. std::string_view编译期处理:consteval重载
用consteval强制要求输入为编译期常量,结合编译期长度校验:
// 编译期获取string_view长度(仅当size为常量表达式时通过) static constexpr size_t get_constant_size(std::string_view sv) noexcept { return sv.size(); } // consteval构造:仅接受编译期已知的string_view consteval fixed_str(std::string_view sv) noexcept { static_assert(get_constant_size(sv) <= length, "String exceeds fixed_str capacity"); const auto copy_len = std::min(sv.size(), length); for (size_t i = 0; i < copy_len; ++i) { chars[i] = sv[i]; } chars[copy_len] = '\0'; } // consteval赋值:仅接受编译期已知的string_view consteval fixed_str& operator=(std::string_view sv) noexcept { static_assert(get_constant_size(sv) <= length, "String exceeds fixed_str capacity"); const auto copy_len = std::min(sv.size(), length); for (size_t i = 0; i < copy_len; ++i) { chars[i] = sv[i]; } chars[copy_len] = '\0'; return *this; }
3. 运行时输入处理:普通重载
针对运行时的const char*或std::string_view,实现自动截断逻辑:
// 运行时const char*构造 constexpr fixed_str(const char* str) noexcept { if (!str) { chars[0] = '\0'; return; } const auto len = std::char_traits<char>::length(str); const auto copy_len = std::min(len, length); for (size_t i = 0; i < copy_len; ++i) { chars[i] = str[i]; } chars[copy_len] = '\0'; } // 运行时const char*赋值 constexpr fixed_str& operator=(const char* str) noexcept { if (!str) { chars[0] = '\0'; return *this; } const auto len = std::char_traits<char>::length(str); const auto copy_len = std::min(len, length); if (std::is_constant_evaluated()) { // 若str是编译期常量但走此重载,强制校验长度 constexpr auto const_len = std::char_traits<char>::length(str); static_assert(const_len <= length, "String exceeds fixed_str capacity"); for (size_t i = 0; i < copy_len; ++i) { chars[i] = str[i]; } } else { memcpy(chars.data(), str, copy_len); } chars[copy_len] = '\0'; return *this; } // 运行时string_view构造 constexpr fixed_str(std::string_view sv) noexcept { const auto copy_len = std::min(sv.size(), length); for (size_t i = 0; i < copy_len; ++i) { chars[i] = sv[i]; } chars[copy_len] = '\0'; } // 运行时string_view赋值 constexpr fixed_str& operator=(std::string_view sv) noexcept { const auto copy_len = std::min(sv.size(), length); if (std::is_constant_evaluated()) { // 尝试编译期校验,仅当sv为常量表达式时触发 if constexpr (requires { get_constant_size(sv); }) { static_assert(get_constant_size(sv) <= length, "String exceeds fixed_str capacity"); } for (size_t i = 0; i < copy_len; ++i) { chars[i] = sv[i]; } } else { memcpy(chars.data(), sv.data(), copy_len); } chars[copy_len] = '\0'; return *this; } // 转换为string_view constexpr operator std::string_view() const noexcept { return { chars.data() }; } };
关键说明
- 字符串字面量通过模板参数推导长度,直接在
static_assert中完成编译期校验 consteval版本的std::string_view重载强制要求输入为编译期常量,否则编译失败- 运行时版本自动截断超长内容,不做编译期校验
- 完全基于C++20原生特性实现,避免了宏的使用
内容的提问来源于stack exchange,提问作者Tomáš Zato
相关产品推荐
相关产品推荐

