Copy-list-initialization assignment has different results for structs with same type members but different definition order

huangapple go评论85阅读模式
英文:

Copy-list-initialization assignment has different results for structs with same type members but different definition order

问题

#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};
}

上述代码中,列表初始化进行了一个临时对象的赋值。
Trace_1Trace_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

我认为在初始化列表中的 0 可以转换为 Trace_1Trace_2 中的成员类型,而不会发生缩小转换,因此列表初始化赋值应该可以工作。您对此有什么看法?

英文:
#include &lt;cstdint&gt;

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};
}

Compiler Explorer link

For above code, the list initialization makes an assignment to one temporary object.
Trace_1 and Trace_2 have the same member types but differ in the declaration order; consequently the assignment result is different.
trace2 = {0} can compile but trace1 = {0} can't, with the error:

&lt;source&gt;:32:12: error: no viable overloaded &#39;=&#39;
    trace1 = {0};
    ~~~~~~ ^ ~~~
&lt;source&gt;:15:8: note: candidate function (the implicit copy assignment operator) not viable: cannot convert initializer list argument to &#39;const Trace_1&#39;
struct Trace_1
       ^
&lt;source&gt;:15:8: note: candidate function (the implicit move assignment operator) not viable: cannot convert initializer list argument to &#39;Trace_1&#39;
struct Trace_1

I think the initializer 0 in the initialization list can be converted to the member types in both Trace_1 and Trace_2 without narrowing conversion, so the list initialization assignment should work. Any thoughts here?

答案1

得分: 5

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

  • 对于 Trace_20 用于初始化 iter,它的类型是 uint32_t(OK)
  • 对于 Trace_10 用于初始化 option_t,这是一个 enum class(ERROR)

你可能会认为对于 Trace_1 使用 0 应该是有效的,因为 option_t{0} 是有效的(自C++17以来)。然而,成员的初始化使用复制初始化进行,就像这样:

option_t option = 0;

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

顺便说一下,当尝试使用 {0} 来初始化(而不是赋值)trace1 时,GCC 给出了更好的错误信息:

error: cannot convert 'int' to 'option_t' in initialization
   24 |     Trace_1 trace1{0};
      |                    ^
      |                    |
      |                    int

可能的解决方案

// 工作,因为现在我们使用直接列表初始化,而不是复制初始化
trace1 = {option_t{0}};

// 工作,因为现在我们使用 Trace_1 中的默认成员初始化器
trace1 = {};

// 与上面相同;更加明确,略微冗余的风格
trace1 = Trace_1{};

注意

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

注意2:enum struct 是允许的,但你可能更喜欢使用enum class,因为这是一种更流行的风格,不会让人感到意外。

英文:

In aggregate initialization, all of the initializers (in your case just 0) are used to initialize the data members in declaration order.

  • for Trace_2, the 0 initializes iter, which is of type uint32_t (OK)
  • for Trace_1, the 0 initializes option_t which is an enum class (ERROR)

You might assume that using 0 must be valid for Trace_1, because option_t{0} is valid (since C++17). However, the initialization of members takes place using copy initialization, as if by:

option_t option = 0;

There is no implicit conversion from int to option_t, so this is not allowed. It's not about whether 0 -&gt; option_t is a narrowing conversion, it's about the syntax = 0 never being allowed for scoped enumerations (enum class).

On a side note, GCC gives us a much better error when attempting to use {0} to initialize (not assign) trace1:

error: cannot convert &#39;int&#39; to &#39;option_t&#39; in initialization
   24 |     Trace_1 trace1{0};
      |                    ^
      |                    |
      |                    int

Possible Solutions

// 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{};

Notes

Note 1: You don't need = {0} to value-initialize a struct in C++, only in C. Don't do it unless you want to be explicit about initializing the first member to zero.

Note 2: enum struct is allowed, but you might want to prefer enum class, since this is a much more popular style and will surprise fewer people.

huangapple
  • 本文由 发表于 2023年7月13日 14:49:26
  • 转载请务必保留本文链接:https://go.coder-hub.com/76676619.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定