向size_t添加const会导致编译失败,这是标准行为吗

Is it standard behaviour that adding const to size_t can cause compile failure?

本文关键字:失败 标准 编译 size 添加 const      更新时间:2023-10-16

我最近读了一篇很酷的文章:https://akrzemi1.wordpress.com/2015/08/20/can-you-see-the-bug/在ideone上玩精简版我得到了令人惊讶的行为:

#include <iostream>
#include <cassert>
using namespace std;
int main() {
    const size_t sz=258;
    string s{sz,'#'};
    assert(2==s.size());
}

不编译,但是删除常量的同一程序编译:

#include <iostream>
#include <cassert>
using namespace std;
int main() {
    size_t sz=258;
    string s{sz,'#'};
    assert(2==s.size());
}

所以我的问题是,这个标准是必需的,还是编译器决定一个是编译器警告,另一个是编译错误。

如果它有帮助的话,这里是来自gcc 5.1(tnx-godbolt(的错误和警告

错误:正在缩小从"size_t{aka long"转换为"258ul"的范围unsigned int}"转换为{}内的"char">


警告:正在缩小"sz"从"size_t{aka long"的转换unsigned int}"转换为{}[-Wstricing]内的"char">

好人clang 3.6在这两种情况下都给出了错误,但问题仍然存在,一个是合法的、坏的C++,另一个是非法的C++?

std::string的构造函数声明为:

string::string(std::initializer_list<char>);
string::string(std::size_t, char);

当我们进行列表初始化时,以下规则适用:

(N3337 [dcl.init.list]/3):类型T的对象或引用的列表初始化定义如下:

  • […]
  • 否则,如果T是类类型,则考虑构造函数枚举适用的构造函数并且通过过载分辨率(13.3,13.3.1.7(来选择最佳的。如果窄化转换(参见以下(来转换任何参数,则程序格式不正确

由于以下规则,选择了初始值设定项列表构造函数:

(N3337 [over.match.list]/1):当非聚合类类型T的对象被列表初始化时(8.5.4(,重载解析在两个阶段选择构造函数:

  • 最初,候选函数是类T的初始值设定项列表构造函数(8.5.4(参数列表由初始值设定项列表作为单个参数组成
  • […]

现在,由于初始值设定项列表构造函数是最佳选择,但转换参数需要缩小转换范围,因此程序格式不正确。

然而,我不认为这会使一个编译器正确,另一个不正确:

(N3337 [intro.compliance]/8):符合要求的实现可能具有扩展(包括额外的库函数(,前提是它们确实具有不会改变任何格式良好的程序的行为需要实施来诊断以下程序使用根据本国际标准形成不良的此类扩展。然而在这样做之后,他们可以编译和执行这样的程序

该标准没有警告与错误的概念,因此GCC在发布警告诊断,然后继续编译程序时是一致的。请注意,如果通过-pedantic-errors,GCC发出错误。

问题是std::string有一个初始化器列表构造函数,而且它是贪婪的。{sz,'#'}被计算为初始值设定项列表,当它将sz转换为char类型以生成std::initializer_list<char>时,您会收到一个缩小转换范围的警告。您可以通过使用()而不是{} 调用constructor来解决此问题

由于initializer_list<char>构造函数是首选的,因此这里的范围缩小了。

N4296 8.5/17.1

--如果初始值设定项是一个(非括号(支撑的init列表对象或引用被列表初始化(8.5.4(。

由于string有构造函数,它需要initializer_list<char>,所以它将被优先提供,因为

8.5.4/2

构造函数是初始值设定项列表构造函数,如果它的第一个参数的类型为std::initializer_list或可能引用cv限定了某些类型E的std::initializer_list,并且没有其他参数,或者所有其他参数都有默认参数(8.3.6(。[注意:Initializer列表构造函数为在列表初始化中优于其他构造函数(13.3.1.7(。

13.3.1.7/1

当非聚合类类型T的对象被列表初始化时8.5.4规定根据根据本节中的规则,重载解析选择两个阶段的构造函数

最初,候选函数是初始值设定项列表类T的构造函数(8.5.4(,参数列表包括初始值设定项列表作为单个参数。

并且由于程序不正确

N4296 8.5.4/3.5

否则,如果T是类类型,则考虑构造函数。这个枚举适用的构造函数并选择最佳构造函数通过过载分辨率(13.3,13.3.1.7(。如果需要转换(见下文(来转换任何参数,这个程序格式不正确

基本上,gcc只会在这个片段上发出警告:

int main() {
   const size_t v = 258;
   char c = {v};
}

clang什么时候会出错。