首页 > 解决方案 > 未命名的匿名联合上的 [[no_unique_address]] 是否有效?

问题描述

tl;博士:是

struct s {
  [[no_unique_address]] union {};
  [[no_unique_address]] union {};
};

有效的?如果是,应该sizeof(s) == 1吗?


我一直在搞各种黑客来解决https://github.com/woboq/verdigris/issues/31。简而言之,问题是我需要在类函数声明之后放置一个宏,并且没有中间分号,这会使clang-format. 例如,给定:

#define W_SIGNAL(name) { /* body */ } static void name##Helper() {}

这个:

class widget {
public:
  void sig1() W_SIGNAL(sig1)
  void sig2() W_SIGNAL(sig2)
  void sig3() W_SIGNAL(sig3)
private:
  int i1;
  int i2;
  int i3;
};

被格式化为

class widget {
public:
  void sig1() W_SIGNAL(sig1) void sig2() W_SIGNAL(sig2) void sig3()
      W_SIGNAL(sig3) private : int i1;
  int i2;
  int i3;
};

不幸的是,由于似乎保留了禁用格式化区域的上下文,因此// clang-format off/on并不能完全解决问题。clang-format例如,这个:

class widget {
public:
// clang-format off
  void sig1() W_SIGNAL(sig1)
  void sig2() W_SIGNAL(sig2)
  void sig3() W_SIGNAL(sig3) 
// clang-format on
private:
  int i1;
  int i2;
  int i3;
};

格式化为:

class widget {
public:
  // clang-format off
  void sig1() W_SIGNAL(sig1)
  void sig2() W_SIGNAL(sig2)
  void sig3() W_SIGNAL(sig3)
      // clang-format on
      private : int i1;
  int i2;
  int i3;
};

// clang-format on需要放在后面int i1才能得到预期的行为,这有点不直观。

clang-format如果在 之后添加分号,则格式符合预期W_SIGNAL(<name>),但这些分号会触发-Wextra-semi警告。额外的分号是相当无害的,因此可以毫不费力地禁用警告,但为了这个问题,让我们假设我不能禁用它。

在宏定义的末尾添加一个未命名的匿名联合允许在不触发的情况下添加结束分号-Wextra-semi

#define W_SIGNAL(name) {} static void name##Helper() {} union {}

所以这:

class widget {
public:
  void sig1() W_SIGNAL(sig1);
  void sig2() W_SIGNAL(sig2);
  void sig3() W_SIGNAL(sig3);
private:
  int i1;
  int i2;
  int i3;
};

按预期格式化,并且不会触发-Wextra-semi. Clang 和 MSVC 确实会发出不同的警告(分别是-Wmissing-declarations/ C4408),但是这些警告可以_Pragma在宏本身中消失,并且对用户有效地不可见。

然而,匿名联合改变了包含类的大小,因此sizeof(widget) > 3 * sizeof(int)). 我很好奇 C++20 的[[no_unique_address]]属性在这里是否有帮助,因为联合是空的并且可以想象可以折叠在一起。当我尝试它时,结果发现 Clang 接受了该属性并按我的预期进行了优化,但 GCC 忽略了该属性,MSVC 拒绝了它。

海合会:

<source>:1:66: warning: attribute ignored in declaration of 'union widget::<unnamed>' [-Wattributes]
    1 | #define W_SIGNAL(...) { /* body */ } [[no_unique_address]] union {}
      |                                                                  ^
<source>:3:15: note: in expansion of macro 'W_SIGNAL'
    3 |   void sig1() W_SIGNAL(sig1);
      |               ^~~~~~~~
<source>:1:66: note: attribute for 'union widget::<unnamed>' must follow the 'union' keyword
    1 | #define W_SIGNAL(...) { /* body */ } [[no_unique_address]] union {}
      |                                                                  ^
<source>:3:15: note: in expansion of macro 'W_SIGNAL'
    3 |   void sig1() W_SIGNAL(sig1);
      |   
<snip>
<source>:13:30: error: static assertion failed
   13 | static_assert(sizeof(widget) == 3 * sizeof(int));
      |               ~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~

MSVC:

<source>(3): error C7545: attribute 'no_unique_address' can only be applied to a non-static data member that is not a bitfield
<source>(4): error C7545: attribute 'no_unique_address' can only be applied to a non-static data member that is not a bitfield
<source>(5): error C7545: attribute 'no_unique_address' can only be applied to a non-static data member that is not a bitfield
<source>(13): error C2607: static assertion failed

给联合一个成员名称(例如,[[no_unique_address]] union {} W_MACRO_CONCAT(u, __LINE__))使代码有效,只有 MSVC 由于失败而发出错误static_assert。但是,这可能会污染 IDE/LSP 自动完成列表,这并不理想。

所以,忽略我出于某种原因而忽略的所有相对微不足道的解决方法,这里哪个编译器是正确的?

标签: c++language-lawyerc++20

解决方案


未命名的匿名联合上的 [[no_unique_address]] 是否有效?

似乎没有禁止它的规则。

sizeof(s) 应该 == 1 吗?

可以但是“应该”有点太强了。它是实现定义的。

[介绍对象]

如果对象具有非零大小...否则,如果对象是基类子对象...否则,对象具有零大小的情况由实现定义。


至于格式,联合技巧似乎是一个可怕的黑客。我建议如下:

void sig1() { W_SIGNAL(sig1) }

鉴于宏定义了函数的主体,这也提高了可读性,因为很明显正在定义一个函数。


推荐阅读