为什么我不能将枚举类型作为另一个枚举类型的基础类型?

5

为什么这段代码不是有效的 C++ 代码?

enum foo : unsigned { first_foo, second_foo };
enum bar : foo { best_foo = first_foo };

GCC 5.4.0表示:
/tmp/a.cpp:3:16: error: underlying type ‘foo’ of ‘bar’ must be an integral type
     enum bar : foo { best_foo = first_foo };

我可以理解如果foo是一个float、结构体或其他内容,为什么会出现这个错误。但从语义、类型安全等方面来看,这似乎对我来说是完全合法的。我错过了什么?


2
为什么?因为标准就是这么规定的。 :) - Zereges
1
@Zereges:但是...为什么? - einpoklum
1
通常的原因是,一些人写了一个提案,并且他们所提出的内容被接受了。 - Bo Persson
@Klaus:请看我在这里的动机(http://stackoverflow.com/q/40607928/1593077)。但你说得很有道理,也许你应该把它作为一个答案。 - einpoklum
继承将是一个可怕的特性。类型为 enum bar 的变量是否可以转换为类型为 enum foo 的变量?反之亦然?这如何与 enum class 交互?这如何使 std::common_type 更加复杂?这个特性的一个用例是什么? - KevinZ
显示剩余2条评论
4个回答

10

C++11 [dcl.enum]/2:

枚举类型的enum-base必须命名为整数类型;任何cv限定符都将被忽略。

枚举类型本身并不是整数类型,根据[basic.fundamental]/7:

bool, char, char16_t, char32_t, wchar_t, 以及带符号和无符号整数类型这些类型统称为整数类型

附有一段非规范性注释:

因此,枚举类型不是整数类型。但是,按照[conv.prom]中规定,枚举类型可以提升为整数类型。

然而,要实现我想你正在寻找的效果仍然很简单:

enum bar : std::underlying_type<foo>::type { best_foo = first_foo };

好的,那是一个直接的形式原因 - 但为什么 type-specifier-seq 必须命名一个整数类型而不是一个整数类型或枚举类型呢? - einpoklum
@einpoklum:这对于这种类型的网站来说是一个广泛的问题。我不在C++委员会上,所以我不确定我怎么能知道。;-] - ildjarn
有时候,委员会成员或者跟进其工作的人会经常访问这个网站并回答问题。Bjarne甚至曾经回答过我的问题。 - einpoklum
@einpoklum:我并不是说你不能运气好,但如果你期望那种等级的答案,你很可能会失望。 - ildjarn
1
@einpoklum 我猜他们认为这不是很重要。如果你想改变它,可以写一份提案 :)。 - TartanLlama
1
受到这个答案的启发,我检查了整数类型的语法。http://en.cppreference.com/w/cpp/language/type [整数类型(也请参阅std::is_integral)] - ctc chen

5
当你向C++添加东西时,你往往只会添加解决问题的最小量。使用"enum A:int"语法,你可以准确地指定"enum A"存储为整数的方式。这就是它的全部作用,并且它解决了问题。"enum A:B"中,B是一个枚举类型,可能有多种含义。A可能扩展B,或者A可能是B底层类型的子集,但具有不同的名称,或者A可能是一个严格限制为仅包含B中可存储值子集的枚举类型,或者A可能是一个其值受限于B值"hull"的枚举类型。在这些方案中,"相同的底层类型"解决方案(使用":int"语法)与"enum A:std::underlying_type_t<B>"一致。因此,已经有一种方法可以实现这个目标,而且并不特别繁琐。除非有人提出了"enum A:B"应该表示什么,并说服了委员会中足够多的人,否则它不太可能被允许。当有多个不同的合理含义时,这使得选择任何一种含义都更加困难。需要强有力的用例来说明为什么一个特定的含义是最好的,以及为什么值得付出努力将其纳入标准中。

好的,那么,如果您愿意,可以看一下我的用例,并发表您对其吸引力的看法。 - einpoklum

3

好的,虽然不完全相同,但我认为您可以使用 std::underlying_type

enum foo : unsigned { first_foo, second_foo };
enum bar : std::underlying_type_t<foo> { best_foo = first_foo };

2
@einpoklum嗯,你在问题中并没有提到这一点,我怎么可能知道呢?我也不知道为什么它不能满足你的需求;通常的enum在涉及底层类型转换时相当薄弱。 - Bartek Banachewicz

3

对我来说,说一个枚举可以基于另一个枚举是一种逻辑错误。如果你认为基础是存储类型,在其中定义了枚举中的值,我无法从逻辑表达式中得出存储类型是枚举的结论。因为枚举不仅仅是存储类型,它还包含一组有效值。

如果我们可以写出类似这样的内容:

enum A: int { ONE, TWO };

这是一个关于“意义”的问题:

enum B: A{};

由于A不仅定义了底层类型(我称之为存储类型),还定义了一组有效值,因此B应该是A枚举的一个子集,这意味着您只能定义已在A中定义的值。

现在是否可以说我们已经在B中定义了ONE、TWO等值? 现在是否可以添加更多值,例如:

    enum B: A{THREE};

所有有效值现在是ONE、TWO、THREE吗?
还是我们只能得到其中的子集含义:
    enum B: A{ONE};

这意味着B只能使用A中已定义的值。仅仅这一点就使我很难将枚举作为另一个枚举的基础。如果你打开了这扇门,你也可以想到想要使用其他类型(例如位字段)的底层存储类型。
struct A { unsigned int a : 3; unsigned int B: 2; };

应该然后

enum B: A { ONE, TWO };

它也是有效的吗?我不认为!;)

从逻辑角度来看(我的观点),枚举的底层类型是其存储类型。让一个枚举成为另一个枚举的底层存储类型毫无意义。


1
“should Be only be a subset of the enum A”? 是的,我认为是这样。 - einpoklum
如果只能是一个子集,那么它就与从结构体/类派生相反。因为你只能在派生的基类中扩展一个基类。所以,你认为将这种逻辑反过来用于枚举类型是合理的吗?;) - Klaus
Klaus:那不是真的。继承通常用于限制基类而不是扩展它。在继承的类之间,既有“是一种也是”的关系,也有“是一种仅仅是”的关系。 - einpoklum

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