问题 三元运算符中的integral_constants


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行为,那么用于推断这两种不同类型的常见类型的转换序列是什么?


2828
2018-02-23 16:38


起源

好吧,它们都可以隐式转换为 int,我认为这是他们的共同类型。 - user2079303
它们是,但我正在尝试决定是否允许编译器寻找匹配。那是不明确的部分。 - Crazy Eddie
相关问题? - François Andrieux
@FrançoisAndrieux嗯,显式强制转换使得它非常简单,因为integral_constants可以隐式转换为它们的底层类型。对于您链接的问题也是如此 - 派生到基本转换可能是转换序列到其中一个操作数的一部分。但我没有在cppreference中看到任何显示编译器会搜索的内容 一些 类型两个操作数都可以转换为 - 它应该是结果的一个或另一个操作数的类型。如果是 integral_constant 结果类型不是操作数中的任何一个,这就是为什么它在gcc / clang上编译的原因令人困惑 - Rostislav
@Rostislav链接问题的已接受答案引用标准的部分列出了我认为可能相关的允许转换。 - François Andrieux


答案:


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


从[expr.cond]:

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

所以我们试图从类型中形成隐式转换序列 std::integral_constant<int, 1> 输入 std::integral_constant<int, 2>。这不可行。反向的隐式转换序列也不可行。这些类型根本不可互换。

所以我们继续:

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

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

否则,结果是prvalue。如果第二个和第三个操作数不具有相同的类型,则任何一个   具有(可能是cv-qualified)类类型,重载决策用于确定转换(如果有)   适用于操作数(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]:

否则,应用如此确定的转换,并使用转换的操作数代替本节其余部分的原始操作数。

执行Lvalue-to-rvalue(4.1),数组到指针(4.2)和函数到指针(4.3)标准转换   在第二和第三个操作数上。完成转换后,以下其中一项应成立:
   - 第二和第三个操作数具有相同的类型;结果是该类型,并使用所选操作数初始化结果对象。

此时,第二和第三个操作数  具有相同的类型: int。所以结果对象初始化为 int,表达形式良好。


8
2018-02-23 17:26





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

否则,结果是prvalue。如果是第二和第三个操作数   不具有相同的类型,并且具有(可能是cv-qualified)   类类型,重载决策用于确定转换   (如果有的话)应用于操作数(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是错误的。


6
2018-02-23 17:27