为啥具有固定底层类型 char 的枚举值解析为 fct(int) 而不是 fct(char)?

Posted

技术标签:

【中文标题】为啥具有固定底层类型 char 的枚举值解析为 fct(int) 而不是 fct(char)?【英文标题】:Why does a value of an enum with a fixed underlying type of char resolve to fct(int) instead of fct(char)?为什么具有固定底层类型 char 的枚举值解析为 fct(int) 而不是 fct(char)? 【发布时间】:2012-12-21 19:11:03 【问题描述】:

回复this question about overload resolution with enums时出现这个问题。

虽然long long 的情况绝对是 MSVC2012NovCTP 中的一个错误(根据标准文本和 gcc 4.7.1 的测试),但我无法弄清楚为什么会出现以下行为:

#include <iostream>

enum charEnum : char  A = 'A' ;

void fct(char)       std::cout << "fct(char)"      << std::endl; 
void fct(int)        std::cout << "fct(int)"       << std::endl; 
void fct(long long)  std::cout << "fct(long long)" << std::endl; 

int main() 

    fct('A');
    fct(A);

MSVC2012NovCTP 和 gcc 4.7.1 都同意这个输出:

fct(char) fct(int)

不应该将AcharEnum 转换为char 吗?为什么A 被转换为int

编辑:clang 抱怨电话不明确,这与我在下面的解释一致;也就是说,如果仅将其视为底层类型,我仍然会发现它更加直观。


两个相关的标准摘录是 §7.2/9:

枚举数或非作用域枚举类型的对象的值通过整数提升(4.5)转换为整数

以及§4.5/4:

其基础类型固定(7.2)的无作用域枚举类型的纯右值可以转换为其基础类型的纯右值。此外,如果可以将整型提升应用于其基础类型,则其基础类型固定的无作用域枚举类型的纯右值也可以转换为提升的基础类型的纯右值。

所以charEnum 可以转换为char,或者char 的任何积分提升,例如int

但这对我来说很模糊,因为“可以”并不能完全说明实际会选择哪个。如果有的话,这应该与此措辞模棱两可,因为在 char 或其任何促销活动之间没有优先考虑。如果您注释掉fct(int),则调用 不明确。为什么int 很特别?

我唯一能想到的是积分提升是递归应用的,但我没有看到强制它。

【问题讨论】:

只是猜测:为了保持旧代码正常工作,因为在 C++11 之前,枚举转换为 int。现在使用类型说明符(在您的情况下为char,我不知道如何正确调用它),我们只想说应该使用什么类型来表示它,而不是在转换中如何处理它。 @leemes:嗯,有点。 “旧式”枚举转换为其基础类型,即intunsigned intlong 等,直到正确保存所有枚举值。所以这个会转换为unsigned int:enum foo X = 0, Y = UINT_MAX ;。不管你的观点仍然成立:它从来没有“少于”int。但是由于固定底层类型的整个概念是新的,因此在没有兼容性的情况下打破直觉是很奇怪的。 在我看来,标准对整体促销的描述存在缺陷。提到的所有其他类型都被描述为能够提升为零或一个精确指定的提升类型。因此,当标准的其他部分说“整体推广”时,每个人都清楚它是做什么的。但是这里听起来好像有两种可能的提升类型?? 看看这个:ideone.com/8zuGEp - 编辑:嗯,我认为它没有显示任何新内容。我们可以重现一些赞成/反对我的论点的东西吗? 哦,等等...也许“最好”的转换是最大尺寸的转换(基础类型,默认类型)? (对于默认类型,我的意思是如果我们不指定类型,枚举将具有的类型)。至少,这可以解释为什么在我的 ideone 代码中,fct(A) 使用 uint,而不是 int,因为它不能... 【参考方案1】:

在 C++03 中,规则是:

无范围枚举类型 (7.2 [dcl.enum]) 的右值可以是 转换为以下第一种类型的右值,可以 表示枚举的所有值(即 7.2 [dcl.enum] 中描述的范围 bmin 到 bmax): int, unsigned int, long int、unsigned long int、long long int 或 unsigned long long int。

在 C++03 编译器中,int 会被选中,因为它是第一个 在名单上。


在 C++11 中,引入了底层类型。因此,通过685. Integral promotion of enumeration ignores fixed underlying type ,此措辞已更改为您在 §4.5/4 中引用的段落,从阅读缺陷报告来看,委员会的意图似乎是选择fct(char)(基础类型)。

但是,根据core issue 1601 下的讨论,C++11 中的文本实际上使转换模棱两可(fct(char)fct(int) 都可以,但都不是首选)。

C++14 提出并接受了以下修复:

促进其基础类型为的枚举的转换 固定到它的底层类型比一个提升到 提升的基础类型(如果两者不同)。

由于它在 C++11 中被报告为缺陷,因此编译器应在 C++11 模式下应用此修复并调用 fct(char)

【讨论】:

嗯,听起来很合理,但我刚刚测试了 clang 3.3,它抱怨说所有三个重载都是模棱两可的。我真的认为这种情况模棱两可是愚蠢的,但clang的最接近我的想法,而你的答案最接近人们的希望。是 clang 错误还是所有潜在的促销活动都同时适用? 根据重载解析规则,调用是模棱两可的,正如您所提到的,您将§4.5/4 解释为没有偏好。因此,基于此,clang 是正确的。但是,根据我在缺陷报告中看到的内容以及我对§4.5/4 的意图的理解,我认为应该选择fct(char) Clang 4.1(即 Apple 的 3.1)更喜欢 fct(int),并且只有在删除该定义时,它才会声明其余定义不明确。因此,Clang 似乎最近更改为使用 GManNickG 描述的行为。根据我的阅读,这也符合标准(例如,升级到任何整数类型都具有相同的等级,因此应该是模棱两可的)。听起来 Jesse 的赤字报告表明这是一个错误,应该选择 fct(chat) @KevinBallard 为什么int 应该很特别? @Potatoswatter:大概是因为在 C++03 中,一个无作用域的枚举可以隐式转换为int,然后可以从那里转换为其他整数类型,因此解决函数重载有利于int 是首选,因为它是较短的转换序列(例如 enum => int,而不是 enum => int => 其他整数)。但是 C++11 改变了这一点,所有转换 enum => 积分都具有相同的等级,而 Clang 3.3 现在显然尊重这一点。【参考方案2】:

根据我对当前标准的解释,调用必须是模棱两可的。跟随解释。

每 4.5/4:

“其基础类型是固定的 (7.2) 的无作用域枚举类型的纯右值可以转换为其基础类型的纯右值。此外,如果可以将整数提升应用于其基础类型,则基础类型固定的无范围枚举类型也可以转换为提升的基础类型的纯右值。”

这提供了两种可选的促销:提升到底层类型,提升到提升底层类型。因此,仅这一段就在解决对重载函数的函数调用时应该使用这些替代方案中的 引入了歧义。

然后,第 13.3.3 段根据 “转换序列” 决定哪个是重载集的最佳可行函数。特别是,与此相关的是 13.3.3.1(“隐式转换序列”),更具体地说,是 13.3.3.1.1(“标准转换序列”),它定义了这些转换序列由哪些基本步骤组成。

13.3.3.1.1/1 和表 12 将这些步骤分为四类,其中 PromotionConversion,并根据个人的类别对转换序列进行排序构成这些序列的转换。

在我们的例子中,我们有两个由单个Promotion 步骤组成的单长度转换序列(4.5./4 都允许)。

转化序列按照 13.3.3.2 进行排名。特别是,13.3.3.2/3 提到转换序列 S1 比转换序列 S2 更可取,如果:

"排名[即提升、转化等]S1的排名优于S2的排名,或者S1和S2的排名 >相同的等级并且可以通过以下段落中的规则进行区分,或者,如果不是,[...]"

提到的“下面的段落”是13.3.3.2/4,它说:

“标准转换序列按其排名排序:精确匹配比提升转换更好,提升比转换更好。具有相同排名的两个转换序列无法区分,除非一个以下规则适用:"

然后,接下来是一组不适用于的规则。

因此,我们有两个单步转换序列,由具有相同排名的单个 Promotion 组成。根据以上对现行标准的解释,调用必须是模棱两可的

但是,我个人同意需要进行更改,以使转换为无范围枚举的固定基础类型优于其他可能的转换。

【讨论】:

你把积分提升和积分转换搞混了。无范围枚举类型到整数类型始终是 Promotion not Conversion。尽管它们都进行了转换(请注意,带有小“c”的转换可以同时指代两者)。 @JesseGood:所以转换为intPromotion,而转换为char(基础类型)是Conversion?如果是这种情况,那么 13.3.3.2/4 解释了为什么接受 int 的重载优先于接受 char 的重载 不,他们都是Promotions。所以你是正确的,他们有相同的等级。此外,clang 3.3 抱怨所有三个调用都是模棱两可的as mentioned here,这支持了这一点。此外,在 C++03 中,规则不同,这解释了为什么选择 int @JesseGood:你说得对,实际上我一直很愚蠢:第 4.5 段的标题是“整体促销”......我将编辑我的答案。谢谢

以上是关于为啥具有固定底层类型 char 的枚举值解析为 fct(int) 而不是 fct(char)?的主要内容,如果未能解决你的问题,请参考以下文章

Swift之深入解析枚举enum的底层原理

C#枚举型可以是int,chr,long吗

SQLite数据类型

复习系列之枚举

oracle 更新一个列 原来的字段为 ‘你好么’ 更新成 ‘你好’ 为啥 会变成‘你好 ’即多了两个空格。

static_cast 从底层类型值到枚举类并切换以获取编译器帮助