对于具有相同类型成员但定义顺序不同的结构体,复制列表初始化赋值具有不同的结果

Liu*_*ick 2 c++ enums initialization language-lawyer

#include <cstdint>

enum struct option_t : uint8_t
{
    INVALID         = 0,
    RESERVED        = 1,
    TEST_OPTION     = 2
};

struct Trace_1
{
    option_t   option  {option_t::INVALID};
    uint32_t   iter    {0};
};

struct Trace_2
{
    uint32_t   iter    {0};
    option_t   option  {option_t::INVALID};
};


int main()
{
    Trace_1 trace1{};
    Trace_2 trace2{};
    trace2 = {0};
    trace1 = {0};
}
Run Code Online (Sandbox Code Playgroud)

编译器资源管理器链接

对于上面的代码,列表初始化对一个临时对象进行了赋值。 Trace_1成员类型相同Trace_2但声明顺序不同;因此分配结果不同。 trace2 = {0}可以编译但是trace1 = {0}不能编译,报错:

<source>:32:12: error: no viable overloaded '='
    trace1 = {0};
    ~~~~~~ ^ ~~~
<source>:15:8: note: candidate function (the implicit copy assignment operator) not viable: cannot convert initializer list argument to 'const Trace_1'
struct Trace_1
       ^
<source>:15:8: note: candidate function (the implicit move assignment operator) not viable: cannot convert initializer list argument to 'Trace_1'
struct Trace_1
Run Code Online (Sandbox Code Playgroud)

我认为初始化列表中的初始化程序0可以转换为两者中的成员类型,Trace_1并且Trace_2无需缩小转换,因此列表初始化分配应该有效。这里有什么想法吗?

Jan*_*tke 5

聚合初始化中,所有初始化程序(在您的情况下只是0)都用于按声明顺序初始化数据成员。

  • 对于Trace_20初始化iter,其类型为uint32_t(OK)
  • 对于Trace_10初始化option_tenum class(ERROR)

您可能认为 using0必须对 有效Trace_1,因为option_t{0}是有效的 (C++17 起)。但是,成员的初始化是使用复制初始化进行的,就像:

option_t option = 0;
Run Code Online (Sandbox Code Playgroud)

没有从int到 的隐式转换option_t,因此这是不允许的。这与是否是缩小转换无关,而是与作用域枚举 ( ) 永远不允许的0 -> option_t语法有关。= 0enum class

附带说明一下,当尝试使用{0}初始化(而不是赋值)时,GCC 给我们带来了一个更好的错误trace1

error: cannot convert 'int' to 'option_t' in initialization
   24 |     Trace_1 trace1{0};
      |                    ^
      |                    |
      |                    int
Run Code Online (Sandbox Code Playgroud)

可能的解决方案

// works because we now use direct list init, not copy init
trace1 = {option_t{0}};

// works because we now use the default member initializers in Trace_1
trace1 = {};

// same as above; more explicit, slightly redundant style
trace1 = Trace_1{};
Run Code Online (Sandbox Code Playgroud)

笔记

注 1:您不需要在 C++ 中= {0}对 a 进行值初始化struct,只需在 C 中进行。除非您想明确将第一个成员初始化为零,否则不要这样做。

注 2:enum struct是允许的,但您可能更喜欢enum class,因为这是一种更流行的风格,并且会让更少的人感到惊讶。