枚举位域和聚合初始化

13

以下代码被clang 6.0.0接受,但被gcc 8.2拒绝

enum class E {
  Good, Bad,
};

struct S {
  E e : 2;
  int dummy;
};

S f() {
  return {E::Good, 100};
}

实时 godbolt 示例

GCC 报错

错误:无法将 '{Good, 100}' 从 '<brace-enclosed initializer list>' 转换为 'S'

哪个是正确的?标准中讨论了这种情况吗?


有趣的是,除了32位之外,GCC对任何位域大小都会给出虚假警告10:9: warning: 'S::e' is too small to hold all values of 'enum class E' ,而clang即使枚举范围确实不适合也不会发出警告。也许这种特殊的位域处理部分原因是为什么GCC无法执行聚合初始化的原因之一? - Gem Taylor
2个回答

4
return {E::Good, 100};会执行复制列表初始化来返回值。该列表初始化的效果是聚合初始化
那么S是一个聚合体吗?关于聚合体的描述因所使用的C++版本而异,但在所有情况下,S都应该是一个聚合体,因此应该可以编译通过。Clang(和MSVC)具有正确的行为。
修复很简单。将您的返回语句更改为返回正确类型的对象:
return S{E::Good, 100};

2
从返回语句中使用的列表初始化的描述中,最好提供标准引用,特别是指出为什么聚合初始化不适用于此处。 - M.M
2
@1201ProgramAlarm 我认为你是对的,但我也认为gcc试图在作用域枚举(使用int作为其底层类型)到位域时进行一些缩小转换,这在不是直接的列表初始化中是被禁止的,就像在返回值的情况下一样。我之所以这么认为,是因为如果将枚举转换为非作用域枚举(其中允许隐式转换为int),或者将位域增加到32,使得完美匹配发生,错误就会消失,即使保留初始化为复制列表初始化。 - Jans
经过进一步调查,我改变了我的判断并重写了答案。 - 1201ProgramAlarm

3

这应该是格式良好的,所以这是gcc的一个bug。

我们通过[stmt.return]p2来到聚合初始化,其中写道:

… 带有大括号初始化列表的返回语句通过从指定的初始化列表中进行复制列表初始化([dcl.init.list])来初始化将从函数返回的对象或引用。 …

然后[dcl.init.list]p3.2说:

否则,如果T是一个聚合体,则执行聚合初始化([dcl.init.aggr])。 …

此时,我们可能会想知道这是否是缩小转换,因此不合法,但[dcl.init.list]p7没有涵盖此情况的任何条款,而且[dcl.init.list]中没有其他情况适用于使其不合法。

我们可以看到,使用类似的例子,其中删除了枚举类型,但保留了位字段,既不gcc也不clang给出了缩小转换诊断,我们期望这种情况是这样的,尽管类似问题由[dcl.init.list]p7.4涵盖,但不是不合法的:

struct S2 {
    int e : 2 ;
    int dummy ;
} ;

S2 foo( int x ) {
   return {x, 100} ;
}

在godbolt上实时查看

观察到,在其他情境下,如gcc并没有问题。

S f(E e1, int x) {  
  S s {e1,100} ;
  return s;
}

所以您确实可以使用解决方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接