为何使用std::string::size_type与char初始化std::string会触发编译警告?
你尝试编译这段改编自《Accelerated C++》的代码:
std::string::size_type max_len = width(foo); std::string border{max_len, '*'};
其中width函数返回std::string::size_type类型,但编译器弹出了这样的警告:
narrowing conversion of ‘max_len’ from ‘std::__cxx11::basic_string
::size_type’ {aka ‘long unsigned int’} to ‘char’ inside { } [-Wnarrowing]
std::string border{max_len, '*'};
而另一段类似的代码std::string border{std::string::size_type{10}, '*'};却能正常编译,这就让你摸不着头脑了对吧?咱们来一步步拆解问题:
核心原因:列表初始化的匹配优先级规则
C++里的列表初始化({}语法)有个很关键的优先级逻辑:编译器会优先尝试匹配接受std::initializer_list的构造函数,只有当这个匹配完全走不通的时候,才会考虑其他构造函数。
而std::string恰好提供了一个接受std::initializer_list<char>的构造函数——它的作用是直接用列表里的字符序列初始化字符串(比如std::string s{'a','b','c'}会生成字符串"abc")。
当你写std::string border{max_len, '*'}时,编译器的第一反应是把这两个参数打包成std::initializer_list<char>:
'*'本身就是char类型,完全符合要求;- 但
max_len是std::string::size_type(通常等价于long unsigned int),要把它转成char就属于窄化转换——毕竟long unsigned int的取值范围(比如几十亿)远大于char(一般最多存255)。哪怕你知道width(foo)返回的值很小,编译器在编译期没法确定它的具体数值,所以会判定这个转换存在数据丢失的风险,触发-Wnarrowing警告。
为啥显式写size_type{10}就没问题?
当你用std::string border{std::string::size_type{10}, '*'}时,10是一个编译时常量,而且它的数值完全在char的合法范围内(只要你的编译器不是用特殊的1位char类型😂)。编译器能明确判断这个转换不会丢失数据,所以不会触发窄化警告。本质上这里编译器还是优先尝试了initializer_list<char>的构造函数,只是因为常量值安全,所以通过了检查。
解决办法
如果你就是想用“重复指定次数字符”的构造函数,只需要避开initializer_list的优先级匹配就行:
- 改用圆括号初始化:
std::string border(max_len, '*');——圆括号初始化不会优先匹配initializer_list构造函数,编译器会直接找到你想要的那个接受size_type和char的版本,完全不会触发警告。 - 或者显式引导编译器匹配目标构造函数(不过不如圆括号简洁):
std::string border(static_cast<std::string::size_type>(max_len), '*');
备注:内容来源于stack exchange,提问作者GeometriaDifferenziale

