英文:
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_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
我认为在初始化列表中的 0
可以转换为 Trace_1
和 Trace_2
中的成员类型,而不会发生缩小转换,因此列表初始化赋值应该可以工作。您对此有什么看法?
英文:
#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};
}
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:
<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
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_2
,0
用于初始化iter
,它的类型是uint32_t
(OK) - 对于
Trace_1
,0
用于初始化option_t
,这是一个enum class
(ERROR)
你可能会认为对于 Trace_1
使用 0
应该是有效的,因为 option_t{0}
是有效的(自C++17以来)。然而,成员的初始化使用复制初始化进行,就像这样:
option_t option = 0;
没有从 int
到 option_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
, the0
initializesiter
, which is of typeuint32_t
(OK) - for
Trace_1
, the0
initializesoption_t
which is anenum 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 -> 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 'int' to 'option_t' 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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论