C++14中大括号包裹字符串常量赋值是否合法?g++与Clang编译冲突
C14带大括号赋值:g与Clang的行为差异及解决办法
问题背景
把代码从Linux/g++移植到macOS/Clang时,碰到了编译错误:
error: initializer-string for char array is too long, array size is 1 but initializer has size X (including the null terminating character)
错误出现在WTF构造函数里的带大括号赋值语句,示例代码如下:
#include <stddef.h> struct StringLike { const char *str; size_t len; StringLike() : str(NULL), len(0) {} template <size_t LEN_> StringLike(const char (&litAry)[LEN_]) noexcept : str(litAry), len(LEN_ - 1) {} StringLike &operator=(const StringLike &rhs) {str = rhs.str; len = rhs.len; return *this;} template <size_t LEN_> StringLike &operator=(const char (&strLit)[LEN_]) {str = strLit; len = LEN_ - 1; return *this;} const char *data() const {return str;} size_t length() const {return len;} }; struct WTF { StringLike litStrs[3]; WTF() { litStrs[0] = {"Is "}; litStrs[1] = {"this "}; litStrs[2] = {"legal?"}; } };
所有版本的g++都能编译通过,但Clang全版本都报错。
从C++14标准看这个问题
对于obj = {init}这种带大括号的赋值操作,标准里的逻辑是:
- 先尝试把大括号里的内容转换成
obj的类型对象,这取决于类有没有能处理这个初始化列表的构造函数。 - 你的
StringLike有个模板构造函数,能接受const char(&)[LEN_]类型的参数——字符串字面量正好是const char[N]类型,能直接绑定到这个数组引用。 - 当你写
{"Is "}时,这个单元素的初始化列表会用里面的字符串字面量去匹配构造函数的参数,完全可以构造出一个临时的StringLike对象,然后调用拷贝赋值运算符operator=(const StringLike&)完成赋值。
为啥g++和Clang不一样?
- g++的处理:正确走了上面的流程,识别到
{"Is "}能通过模板构造函数转成StringLike临时对象,所以编译没问题。 - Clang的处理:这里明显是Clang的路径判断出了问题——它错误地把
{"Is "}当成了要初始化一个大小为1的char数组,而字符串字面量长度远超1,所以报了错。这更像是Clang在处理这类赋值场景时的兼容性bug。
解决办法(尽量少改代码)
- 显式构造临时对象:把赋值语句改成
litStrs[0] = StringLike{"Is "};,直接告诉编译器要构造StringLike对象,避开Clang的错误路径。 - 去掉大括号:改成
litStrs[0] = "Is ";,这时会直接匹配模板版的赋值运算符operator=(const char (&strLit)[LEN_]),这是改动最小的方案。 - 新增适配赋值运算符:加一个接受
std::initializer_list<const char*>的赋值运算符,但这个会增加代码复杂度,不如前两种实用。
结论
从C14标准的角度看,g的行为是符合预期的,Clang的报错属于处理此类场景的兼容性问题。优先选前两种方案,既能解决问题,又能最小化代码改动。
内容的提问来源于stack exchange,提问作者barnabas
相关产品推荐
相关产品推荐

