integral_constants三元运算符

integral_constants in ternary operator

本文关键字:三元 运算符 constants integral      更新时间:2023-10-16

MSVC和clang/gcc在三元运算符中是否可以使用两个不同的整数常量(以及它们是否有common_type)方面存在分歧:

#include <utility>
int main()
{
return false
? std::integral_constant<int, 1>() 
: std::integral_constant<int, 2>();
}

上面的代码片段在 clang 和 gcc 中编译得很好,但在 MSVC 中编译不行。根据标准的正确行为是什么?如果是 clang/gcc 行为,那么用于推断这两种不同类型的常见类型的转换序列是什么?

TLDR;代码格式正确。条件表达式将具有类型int和值2。这是一个 MSVC 错误。


From [expr.cond]:

否则,如果第二个和第三个操作数具有不同的类型,并且具有(可能符合 cv 限定的)类类型,或者 [...],则尝试形成从每个操作数到另一个操作数类型的隐式转换序列 (13.3.3.1)。[...]尝试形成从类型T1的操作数表达式E1到与操作数E2表达式的类型T2相关的目标类型的隐式转换序列,如下所示:[...]
— 如果 E2 是左值, [...]
— 如果 E2 是一个 x值, [...]
— 如果 E2 是一个 prvalue,或者如果上述转换序列都不能形成,并且至少有一个 操作数具有(可能符合 cv 条件)类类型:
      — 如果 T1 和 T2 是相同的类类型(忽略 cv 资格),或者一个是另一个的基类, 并且 T2 至少与 T1 一样符合 cv 条件,目标类型为 T2,
否则,目标类型是 E2 在应用左值到右值 (4.1) 后将具有的类型,       数组到指针 (4.2) 和函数到指针 (4.3) 标准转换。

因此,我们尝试形成从类型std::integral_constant<int, 1>到类型std::integral_constant<int, 2>的隐式转换序列。这是不可行的。反向的隐式转换序列也不可行。这些类型根本无法相互转换。

所以我们继续:

如果没有转换 可以形成序列,操作数保持不变,并按照描述执行进一步检查 下面。[...]

如果第二个和第三个操作数是相同值类别的 glvalue 并且具有相同的类型,则 [...]

否则,结果是一个 prvalue。如果第二个和第三个操作数的类型不同,并且 具有(可能符合 CV 条件的)类类型,重载分辨率用于确定要 应用于操作数 (13.3.1.2、13.6)。如果重载解析失败,则程序格式不正确。

好的,我们可以执行什么过载解决方案?来自 [over.match.oper]:

如果任一操作数的类型是类或枚举,则可能会声明实现此运算符的用户定义运算符函数,或者可能需要用户定义的转换才能将操作数转换为 适用于内置运算符的类型。

其中内置在 [over.built] 中指定为:

对于每对提升的算术类型 L 和 R,都存在以下形式的候选算子函数

LR operator?:(bool, L , R );

其中 LR 是类型 L 和 R 之间通常算术转换的结果。

其中一个内置将是int operator?:(bool, int, int).由于std::integral_constant<int, V>确实有一个operator int(),这对两个参数来说都是可行的转换。

我们继续在 [expr.cond]:

否则,将应用这样确定的转换,并在本节的其余部分使用转换后的操作数代替原始操作数。

执行左值到重值 (4.1)、数组到指针 (4.2) 和函数到指针 (4.3) 标准转换 在第二个和第三个操作数上。完成这些转换后,应保留以下条件之一:
— 第二个和第三个操作数具有相同的类型;结果属于该类型,并且使用选定的操作数初始化结果对象。

此时,第二个和第三个操作数确实具有相同的类型:int。因此,结果对象被初始化为int,并且表达式的格式正确。

[expr.cond] 中的相关段落是 6:

否则,结果是一个 prvalue。如果第二个和第三个操作数 没有相同的类型,并且具有(可能符合 CV 条件) 类类型,重载分辨率用于确定转换 (如果有的话)应用于操作数(13.3.1.2、13.6)。如果 重载解析失败,程序格式不正确。否则, 应用这样确定的转换,并转换操作数 用于代替此其余部分的原始操作数 部分。

integral_constant<int>有一个转换运算符来int,所以这可以工作。 在 13.3.1.2 之后,我们看到在第 3.2 段,所有采用整数和浮点参数的内置?:运算符都是候选的。

现在,给定我们的三个参数,对所有这些执行重载解析。根据 [over.ics.rank]/3.3,我们通过比较从int(integral_constant<int>的转换运算符的返回类型)到内置运算符的参数类型的标准转换序列来平局。

但是,看一下表 13 就足够了;对浮点类型的转换具有转换等级,并且由于int是提升类型,因此转换为除int以外的任何整数类型(这是标识转换)是具有转换等级的积分转换。因此,最可行的候选人无疑是operator?:(bool, int, int).也就是说,MSVC 是错误的。