您可以取消引用临时数组吗?

Can you dereference a temporary array?

提问人:Jan Schultke 提问时间:9/6/2023 最后编辑:Jan Schultke 更新时间:9/9/2023 访问量:372

问:

请考虑以下代码:

void foo() {
    int arr[1];
    *arr; // OK

    using T = int[1];
    *T{}; // OK for Clang and MSVC
          // GCC error: taking address of temporary array
}

请参阅编译器资源管理器中的实时代码

我的直觉是,这应该会导致数组到指针的转换,并且间接格式良好。 但是,我对此并不完全确定。*T{}

GCC 是对的,还是这是一个错误?这是故意的,以防止开发人员犯错误吗?毕竟,您通常不会取消引用数组。这在任何地方都有记录吗?

免責聲明
CWG 第 2548 期已确认“通过数组 prvalue 进行间接操作在今天也无效”。@StoryTeller 的答案是错误的,并且错误地理解了目标类型的含义,假设这也适用于 ,但此表达式不是指针的初始化。*T{}

更多讨论见编辑问题 EDIT 6555

C++ GCC 语言律师 C++20 临时对象

评论

1赞 Marek R 9/6/2023
编译器资源管理器链接不显示错误消息:((看起来像编译器资源管理器中的错误)。这是更方便的链接: godbolt.org/z/4Mar7dMEq
0赞 KamilCuk 9/6/2023
(T{})[0];工程。为什么 gcc 抱怨取地址?运营商是否正在获取地址?奇怪。*
0赞 Jan Schultke 9/6/2023
@KamilCuk可以简化为 .我想这是因为它是别名的事实,因此间接不是“直接”在数组上。T{}[0]*(T{} + 0)
0赞 Swift - Friday Pie 9/6/2023
*T{}由于类似 MVP 的行为,编译器将其误认为是复合文字的空情况,这是一种 C 结构,但不是合法的 C++。复合文字可能作为 C++ 编译器中的扩展存在,这就是 Clang 和 MSVC 可能正在做的事情,或者他们正确地将其识别为不是这样。
0赞 Jan Schultke 9/6/2023
@Swift-FridayPie,一个复合文字,需要括号。 只是一个类型名称,后跟一个 braced-init-list。对于 来说,这是完全合法的,并且所有编译器都一视同仁。T{}T = int

答:

1赞 StoryTeller - Unslander Monica 9/6/2023 #1

在我看来,它似乎定义得很好,Clang 和 MSVC 会根据需要进行定义。

[expr.unary.op](强调我的)

1 一元运算符执行间接操作:应用它的表达式应是指向对象类型的指针,或指向函数类型的指针,结果是引用表达式指向的对象或函数的左值。*

然后我们有这句话告诉我们何时何地可以应用标准转换序列:

[转换总则]

1 标准转换是具有内在含义的隐式转换。[conv] 枚举了此类转换的完整集合。标准转换序列是按以下顺序排列的标准转换序列

  • 从以下集合中进行零次或一次转换:左值到右值的转换、数组到指针的转换和函数到指针的转换。

...

如有必要,将对表达式应用标准转换序列,以将其转换为所需的目标类型。

这告诉我们,可以应用标准转换序列将操作数转换为表达式所需的类型。这就是它的工作方式,因此我们只需要检查数组到指针的转换是否可以应用于我们的 prvalue 数组。*arr

[转换数组]

1 “N T 数组”或“T 未知边界数组”类型的左值或右值可以转换为“指向 T 的指针”类型的 prvalue。应用临时物化转换 ([conv.rval])。结果是指向数组的第一个元素的指针。

它可以,因为标准甚至明确要求一个临时数组具体化,并且指针指向它的第一个元素。总而言之,GCC 拒绝有效的代码,所以可以说它有一个错误。

评论

0赞 StoryTeller - Unslander Monica 9/6/2023
我无法评论它是否是有意留下的已知错误。我不记得曾经在野外看到过这样的例子,所以我不会责怪 GCC 开发人员。
0赞 Jan Schultke 9/6/2023
乍一看,我认为这个答案是正确的。然而,与直觉相反,隐性转换不会在可能的情况下发生;需要明确说明。这就是为什么 [expr.mul] 指定为算术类型或无作用域枚举定义乘法,即使无作用域枚举可以转换为算术类型。没有任何地方说明数组的间接是需要显式转换的上下文,因此它在这里不适用。GCC是对的。
0赞 StoryTeller - Unslander Monica 9/6/2023
@JanSchultke - 这与直觉相去甚远。那边的“如有必要”使整个主题远未关闭。可能是它本身的缺陷,也可能是故意留下的,这样他们就可以一时兴起改变自己的意见。无论哪种方式,我都不同意你似乎被说服的观点,所以我会保留我的答案。内容分级将发挥其作用,它甚至可能导致标准发生足够明显的变化。如果有一天到来,我会删除它。
0赞 Jan Schultke 9/6/2023
主题没有关闭和关闭。你引用了一段关于隐式转换的段落。上下文很重要。该标准明确规定了何时应用转换以及应用于哪些操作数。例如,[expr.ass] 明确声明右操作数转换为左操作数的类型。如果你认为 [conv.general] 是无处不在的隐式转换的笨拙支票,为什么你认为标准明确规定了这些转换何时发生?
3赞 Language Lawyer 9/7/2023
如有必要,标准转换序列将应用于表达式,以将其转换为所需的目标类型这似乎只适用于初始化,因为“目标类型是正在初始化的对象或引用的类型”。看起来它应该与 timsong-cpp.github.io/cppwp/n4868/dcl.init.general#16.9 一起阅读
2赞 Language Lawyer 9/6/2023 #2

GCC 是正确的,因为间接只能应用于指针。[expr.unary.op]/1

一元运算符执行间接操作:应用它的表达式应是指向对象类型的指针,或指向函数类型的指针,结果 [...]*

注: CWG 1642EDIT 3945 将间接应用于 prvalue 操作数。

*arr是可以的,因为是一个左值,并且 [basic.lval]/6arr

每当 glvalue 显示为需要该操作数的 prvalue 的运算符的操作数时,就会应用左值到右值、数组到指针或函数到指针的标准转换来将表达式转换为 prvalue。

数组到指针的转换 ([conv.array]) 应用于。arr

T{}已经是 prvalue(数组类型),并且这里没有需要数组到指针转换的措辞,所以不行。*T{}

评论

0赞 HolyBlackCat 9/9/2023
同样的事情会发生在吗?[]
0赞 Language Lawyer 9/9/2023
@HolyBlackCat [expr.sub] 说 «其中一个表达式应该是 “数组 ”类型的 glvalue,所以我们可以说 [basic.lval]/7 适用。OTOH,我同意目前尚不清楚为什么我们选择它而不是指向 [expr.sub] 说 «或类型的 prvalue “指针”“ 并声称数组类型的 prvalue 不适合。我认为 [expr.sub] 是运算符同时接受对象类型的 glvalues 和 prvalues 的唯一情况。多年来,我定期考虑提交(社论?)问题,但由于某种原因仍然没有TT
0赞 Language Lawyer 9/10/2023
@HolyBlackCat目前还不清楚我们为什么选择这口井,主要是出于意图。在 C++14 中,在“通过简化值类别保证复制省略”之前,数组 prvalues 表示临时对象,[expr.sub] 应用于数组类型的所有表达式。而“保证复制省略”并不打算打破这一点。
1赞 303 10/6/2023
怎么样?GCC似乎对此没有问题。using T = int(&&)[1]; *T{};
0赞 Language Lawyer 10/7/2023
@303 cplusplus.github.io/CWG/issues/1521