使用已定义的结构作为联合的匿名成员

Using an already-defined struct as an anonymous member of a union

假设我有一个 32 位硬件寄存器 Reg,我希望能够以 32 位值(例如 Reg = 0x12345678)或位域(例如 Reg.lsw = 0xABCD).我可以通过声明一个具有匿名结构成员的联合,并声明赋值和转换运算符 to/from uint32_t 来实现这一点。在小端环境中,代码可能如下所示:

#include <cstdint>
#include <cstdio>

typedef union
  {
  uint32_t val ;
  struct
    {
    uint32_t lsw : 16 ;
    uint32_t msw : 16 ;
    } ;

  operator = (uint32_t n) { val = n ; }
  operator uint32_t() const { return val ; }
  } HWR ;

int main()
  {
  HWR Reg ;
  Reg = 0x12345678 ;
  Reg.lsw = 0xABCD ;
  printf ("%X\n", uint32_t(Reg)) ;
  }

但是现在假设我有一大堆这样的寄存器,每个都有自己的位域布局,并且我有一个头文件 FieldDefs.h 将这些位域布局声明为命名结构。我如何在上面的代码中使用这些命名结构,以便我可以访问 32 位值以及各个位域?我可以这样做:

#include "FieldDefs.h" // Defines struct MyHWR
typedef union
  {
  uint32_t val ;
  struct MyHWR field ;

  operator = (uint32_t n) { val = n ; }
  operator uint32_t() const { return val ; }
  } MyHWRUnion ;

但现在我需要输入 Reg.field.lsw =...

而不是 Reg.lsw =...

有什么方法(在 C++17 中)可以将已定义的结构声明为联合的匿名成员吗?如果重要的话,我正在使用 g++ 7.3.0 版。

union
{
// ...
    struct
    {
    // ...
    };

这是一个匿名结构。匿名结构在 C++ 中是 ill-formed。只有工会可以是匿名的。这与允许匿名结构的 C 不同(自 C11 起)。

Is there any way (in C++17) to declare an already defined struct as an anonymous member of a union?

没有。未命名的成员不能有命名类型。

您需要在未命名成员和 pre-declared class 之间做出选择。鉴于匿名结构首先是 non-standard,我建议使用命名成员和 pre-defined class。也许给它一个简短的名称以尽量减少冗长。

我想 none 会喜欢这个答案,无论是 OP(因为需要 g++ 9.1),还是 C++ 专家(UB 味道?),但我仍然为修补它而感到自豪。

C++20 中有 [[no_unique_address]] attribute,g++9.1 已经支持它(即使没有 -std=c++2a 标志)。

这里怎么用? By test and trials 似乎如果我们创建标有它的代理成员 val 它将获取对象 1.

的地址

因此我们可以创建 Proxy class,其中包含 operator=(uint32_t)operator uint32_t,将 this 视为 uint32_t。代理对象没有地址,不会增加使用它的结构的大小。

位域名称必须通过继承来添加,它被包裹在简单的模板中,以保持命名的一致性 HWR

瞧,我们有 HWR<bitfield> 对象,它可以由 val 成员直接分配给 uint32_t,并提供对位域名称的访问。

https://godbolt.org/z/N2xEmz

#include <bits/stdint-uintn.h>

#include <cstddef>
#include <cstdint>
#include <cstdio>

// Example bifields, I assumed you have such in "FieldDefs.h"
struct bitfield {
  uint32_t lsw : 16;
  uint32_t msw : 16;
};

struct ThisProxy {
  uint32_t& operator=(uint32_t n) {
    auto& uint = *reinterpret_cast<uint32_t*>(this);
    uint = n;
    return uint;
  }
  operator uint32_t() const { return *reinterpret_cast<const uint32_t*>(this); }
};

template <typename Bitfield>
struct HWR : Bitfield {
  static_assert(sizeof(Bitfield) == 4, "Bad things would happen");
  HWR& operator=(uint32_t n) {
    this->val = n;
    return *this;
  }
  operator uint32_t() const { return this->val; }
  [[no_unique_address]] ThisProxy val;
};

int main() {
  HWR<bitfield> Reg;
  // Sanity check that proxy points at &Reg and does not increase size
  static_assert(offsetof(HWR<bitfield>, val) == 0, "");
  static_assert(sizeof(HWR<bitfield>) == 4, "");

  Reg = 0x12345678;
  Reg.val = 0x8765432A;
  Reg.lsw = 0xABCA;
  printf("%X\n%ld\n", uint32_t(Reg), sizeof(Reg));

  return 0;
}

编辑:

事实证明,Reg.val 的访问不是强制性的,继承 + reinterpret_cast 的技巧可以在 pre-C++20 代码中重用。

template <typename Bitfield> struct HWR : Bitfield {
  static_assert(sizeof(Bitfield) == 4, "Bad things would happen");
  HWR &operator=(uint32_t n) {
    *reinterpret_cast<uint32_t *>(this) = n;
    return *this;
  }
  operator uint32_t() const {
    return *reinterpret_cast<const uint32_t *>(this);
  }
};

仍然有 reinterpret_cast 的味道,我需要找出其他东西才能完全推荐此代码。每当位域可以由基础类型 uint32_t 解释时。

1 我不确定何时由 P0840R2.

保证 0 的偏移量

PS。 g++ 抱怨 warning: offsetof within non-standard-layout type ‘HWR<bitfield>’ is conditionally-supported [-Winvalid-offsetof],但我没有尝试找到解决方法。

PPS。没有匿名结构!