【问题标题】:Does struct with reference member have unique object representation?具有引用成员的结构是否具有唯一的对象表示?
【发布时间】:2020-10-24 03:30:14
【问题描述】:

这个answer 提出了以下问题。

假设我们有一个简单的

struct S {
    int& i;
}

在内部(至少在 GCC 和 Clang 中)S 仅包含指向 int 的指针,并且

static_assert(sizeof(int*) == 8);
static_assert(sizeof(S)    == 8);

S 有唯一的对象表示吗? GCC 和 Clang 不同意 *:

static_assert( std::has_unique_object_representations_v<int*>);
static_assert(!std::has_unique_object_representations_v<S>);    // GCC
static_assert( std::has_unique_object_representations_v<S>);    // Clang

哪个编译器在这里,为什么?

* idclev 463035818 注意到 GCC 和 Clang 之间的分歧。

【问题讨论】:

  • MSVC 同意 GCC。
  • @eerorika 这本身就是一件不同寻常的事情! :-)
  • @AdrianMole 好吧,在这种情况下,他们必须至少同意一个其他编译器:)
  • @eerorika:他们本可以忠于传统,根本不执行has_unique_object_representations_v ;)。
  • 引用只是指针的替代语法。引用成员没有意义,它的规范和实现都是错误的。从你的代码库中禁止引用成员比浪费时间推理要容易得多。

标签: c++ c++17 language-lawyer


【解决方案1】:

首先,引用不是对象。对象在 [intro.object] 中指定,在 [dcl.ref] 中指定。

子对象是对象([intro.object])。因此引用成员不是子对象,因此只包含引用成员(并且没有基)的类没有子对象(即使它有数据成员)。

[meta.unary.prop]

模板特化 has_unique_object_representations 的谓词条件应满足当且仅当

  • T 可以简单地复制,并且
  • 具有相同值的任何两个 T 类型的对象具有相同的对象表示,其中数组或非联合类类型的两个对象被认为具有相同的值如果它们各自的直接序列子对象具有相同的值,...

子对象序列是空的,因此等于另一个空序列,因此S 类型的所有对象都具有“相同的值”2 根据此规则。

但是,引用不同对象的对象必然具有不同的对象表示。因此第二个要求1满足。

因此对象表示不是唯一的,Clang 在技术上是错误的,而 GCC 和 MSVC(其结果与 GCC 相同)是正确的。



如果我们得出不满足第二个要求的结论,这已经成为1有点离题了,但是:S 可以简单地复制吗?

static_assert(std::is_trivially_copyable_v<S>);

在 Clang 和 GCC 中都通过,但根据 MSVC,S不可轻易复制的。那么,哪个是正确的?

[class.copy.ctor]

类 X 的复制/移动构造函数是微不足道的,如果它不是用户提供的并且如果:

  • X 类没有虚函数 ([class.virtual]) 和虚基类 ([class.mi]),并且
  • 选择用于复制/移动每个直接基类子对象的构造函数是微不足道的,并且
  • 对于 X 的每个类类型(或其数组)的非静态数据成员,选择用于复制/移动该成员的构造函数是微不足道的;

所有这些都满足。因此S 有一个简单的复制/移动构造函数。

[class.prop]

一个普通可复制的类是一个类:

  • 具有至少一个符合条件的复制构造函数、移动构造函数、复制赋值运算符、移动赋值运算符([special]、[class.copy.ctor]、 [class.copy.assign]),
  • 其中每个符合条件的复制构造函数、移动构造函数、复制赋值运算符和移动赋值运算符都是微不足道的,并且
  • 有一个普通的、未删除的析构函数 ([class.dtor])。

所有人都满意,因此S 可简单复制的,而 MSVC 类型特征则相反。


1 编辑:我最初的结论是倒过来的。

2 在我看来,在考虑类对象的“值”时,是否应该忽略引用数据成员应该是值得商榷的。这种忽略它们的技术性可能会被视为标准中的缺陷。

【讨论】:

  • 你有一个空洞的逻辑倒退:如果引用不是子对象,那么类类型的 所有 对象“具有相同的值”,尽管它们可能引用不同的ints(肯定有不同的对象表示)!
【解决方案2】:

这是来自 Clang 的有意解释

请注意,Clang 明确选择了他们的方法 based on comments from Richard Smith,即使知道 GCC 被拒绝(在 OP 的上下文中)std::has_unique_object_representations_v&lt;S&gt; 并指出这种 GCC 行为可能是一个错误 [emphasis 我的] :

erichkeane 引用不能轻易复制,因此它们会阻止结构具有唯一的对象表示。

rsmith 这对我来说听起来像是错误的行为。 如果两个结构具有绑定到同一个对象的引用,则它们具有相同的对象表示,因此该结构确实具有唯一的对象表示。

erichkeane 我不这么认为...我会注意到 GCC 在其实现中拒绝引用,但这可能是一个错误

rsmith [...] 所以我认为引用(如指针)在被视为类类型。(但__has_unique_object_representations(T&amp;) 仍应返回false,因为T&amp; 不是可简单复制的类型,即使包含T&amp; 的类可能是。)

正如@idclev 463035818 所指出的,Clang 和 GCC agree that S is trivially copyable,这意味着他们的分歧在于具有相同值的(普通可复制)类型S 的两个对象是否具有相同的对象表示。对于后者的答案,请参阅@eerorika's excellent argument(Clang 在技术上是错误的,而相关的标准段落值得商榷)。

【讨论】:

  • 引用不可轻易复制,但这不会影响S。一个类是否可简单复制仅取决于它的类类型成员,int&amp; 不是类类型。见stackoverflow.com/a/30097049/15416
  • fwiw,两者都同意S 可以轻松复制:godbolt.org/z/8oj8sQ
  • @eerorika 感谢您的提示。您更新后的答案为 “因此 Clang 在技术上是正确的,而 GCC 和 MSVC(与 GCC 的结果相同)是错误的。”,而您的最后一条评论表明 Clang 实际上是错误的. Clang 真的错了吗?
  • @eerorika 我明白了,你的回答非常好。再次感谢他们的提示。
  • @dfri 我删除了 cmets。我把编译器都混在了脑子里:D
【解决方案3】:

S 很容易复制,因为它没有类类型的成员,也没有用户声明的成员函数。这没有争议;正如 idclev 463035818 指出的那样,两个编译器都同意std::is_trivially_copyable_v&lt;S&gt;==true

所以问题归结为两个相同的 S 对象是否位相同。由于两种实现都选择将引用表示为指针(一个有效的选择),std::has_unique_object_representations_v&lt;S&gt; 必须匹配std::has_unique_object_representations_v&lt;int*&gt;。因此,GCC 是错误的。

【讨论】:

    猜你喜欢
    • 2014-12-31
    • 2012-03-02
    • 1970-01-01
    • 2022-01-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多