VS 2012 中的条件运算符类型转换

Posted

技术标签:

【中文标题】VS 2012 中的条件运算符类型转换【英文标题】:Conditional operator type conversion in VS 2012 【发布时间】:2013-05-29 08:15:51 【问题描述】:

我目前正在将一个相当大的项目从 VS 2008 转换为 2012,并且在似乎执行条件运算符类型转换的方式上遇到了问题。

首先让我说我接受条件运算符的语义有些复杂,并意识到代码最初所做的可能不正确,但我对现在在 VS 2012 中发生的事情感到非常困惑,我想知道如果有人能准确解释为什么它会这样做。

class DummyString

    wchar_t wchBuf[32];

public:
    DummyString()  *wchBuf = 0; 
    DummyString(int)  *wchBuf = 0; 
    DummyString(const DummyString& ds)  *wchBuf = 0; 

    operator const wchar_t*() const  return wchBuf; 
;

int _tmain(int argc, _TCHAR* argv[])

    DummyString ds;
    // note: the argc test is simply to stop the conditional operator
    // being optimised away
    const wchar_t* pPtr = (argc == 100) ? 0 : ds;
    assert(pPtr == static_cast<const wchar_t*>(ds));
    return 0;

在 VS 2008 中,上面的条件运算符将导致在 ds 上调用 operator const wchar_t*() 方法,并且断言不会触发。也就是说,它会将ds 隐式转换为const wchar_t*

在 VS 2012 中,条件运算符导致以下行为:

临时的DummyString 是通过复制构造函数构造的 然后在该临时副本上执行到 const wchar_t* 的转换

这会导致pPtr 指向一个被破坏的对象,并且断言当然会触发。

现在,如果我从类中删除 DummyString(int) 构造函数,则代码无法在 VS2012 中编译(没有从 'DummyString' 到 'int' 的转换)很明显 0 导致表达式被评估为 int 而不是指针。

但是在这种情况下,为什么不调用DummyString(int) 构造函数将0 转换为DummyString? 为什么编译器会创建ds 的副本,然后将其转换为 wchar_t*,而它可以很容易地对原始对象执行转换?

我很想开悟! :)

【问题讨论】:

您是否尝试过使用nullptr 而不是0,所以这两个操作数被明确地视为指针? 是的,我在调查时确实尝试过,它导致构建错误:没有从 'DummyString' 到 'nullptr' 的转换 第二个和第三个操作数不是同一类型的左值,因此条件表达式的结果是从所选操作数构造的右值。在任何情况下,您都不应该期望结果与DummyStringds 相同。 【参考方案1】:

C++11 标准第 5.16/3 段规定:

[...] 如果第二个和第三个操作数有不同的类型并且有(可能是 cv 限定的)类 类型,或者如果两者都是相同值类别和相同类型的左值,除了 cv-qualification,an 尝试将这些操作数中的每一个转换为另一个的类型。 [...]

还有:

[...] 如果两者都可以 转换,或者可以转换但转换不明确,程序格式错误。如果两者都没有 可以转换,操作数保持不变,进一步检查如下所述。 如果恰好可以进行一次转换,则该转换将应用于所选操作数和转换后的 本节其余部分使用操作数代替原始操作数。

在这种情况下,只有从 intDummyString 的转换是可能的,因为在另一个方向上,const wchar_t* 是我们所能做到的——没有从 const wchar_t* 到的标准隐式转换一个int

这就是为什么如果您删除采用int 的转换构造函数时编译器会报错:如果该构造函数不存在,根据上述段落,程序将是错误的(在任何一个方向都不会存在转换) .

因此,第二个操作数(第一选择)被认为是DummyString(0)

然而,第二个操作数可以转换为DummyString这一事实意味着第二个操作数将被计算。这取决于条件,条件的计算结果为false(除非您将 100 个参数传递给命令行),这就解释了为什么您没有看到对该构造函数的调用。根据第 5.16/1 段:

条件表达式从右到左分组。第一个表达式根据上下文转换为 bool(第 4 条)。 它被评估,如果为真,则条件表达式的结果是第二个表达式的值, 否则是第三个表达式。 只计算第二个和第三个表达式中的一个。 [...]

但是为什么你的断言会失败呢?

为什么编译器会创建ds 的副本,然后将其转换为wchar_t*,而它可以很容易地对原始对象执行转换?

嗯,这是由于第 5.16/4-5 段:

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

否则,结果为纯右值。如果第二个和第三个操作数的类型不同,并且 具有(可能是 cv 限定的)类类型,重载决议用于确定要进行的转换(如果有) 应用于操作数 (13.3.1.2, 13.6)。

0 不是泛左值,因此条件的结果将是纯右值。这意味着当条件为false 时对条件运算符的评估最终会从ds 构造一个临时,这是您观察到的行为。

这是第 5.16/6 段规定的,其中说:

执行左值到右值 (4.1)、数组到指针 (4.2) 和函数到指针 (4.3) 标准转换 在第二个和第三个操作数上。在这些转换之后,应满足以下条件之一:

——第二个和第三个操作数的类型相同;结果就是那种类型。 如果操作数有 类类型,结果是结果类型的prvalue临时值,它是从任一复制初始化的 第二个操作数或第三个操作数取决于第一个操作数的值。 [...]

条件 `"第二个和第三个操作数具有相同的类型" 成立,因为操作数现在在 5.16/3 中描述的转换之后被考虑(参见这个答案的开头)。

要解决您的问题,您可以对第二个参数执行显式转换:

const wchar_t* pPtr = (argc == 100) ? 0 : static_cast<const wchar_t*>(ds);

由于存在从0 到指针类型的标准转换并导致空指针 - 请参阅第 4.10/1 段。

【讨论】:

+1 获取详细答案!你碰巧明白为什么它以前会起作用吗? (无论是 Visual 2008 中的错误还是标准的不同措辞) @MatthieuM.: 我相信是VC9的bug,因为C++03和C++11的相关措辞是一样的 我也是+1。 ——我还不清楚临时对象。我知道必须从 ds 制作prvalue,但不知道为什么这意味着创建临时值。左值(ds)不能直接用作prvalue吗? (不确定我是对 l/rvalues 还是对新的 C++11 语义感到困惑。)——关于 VS2008,似乎它对双方都应用了转换以获得相同的类型?它似乎将 0 从 int 转换为 const wchar_t *,并将 DummyString 从 DummyString 转换为 const wchar_t *。另一方面,VS2012按照标准进行,只将一方的类型转换为另一方的类型。 @JonathanPotter: 如果它们不是两个glvalues。基本上,这里的编译器需要给表达式(e) ? a : b 一个值类别(基本上是glvalue 或prvalue),这必须在编译时确定。如果ab 都是prvalues,或者如果ab 都是glvalues,我们就设置好了。但是,如果一个是glvalue,另一个是prvalue,我们需要从前者中创建一个prvalue——所以编译器构造一个临时的,最终条件表达式的值类别是prvalue @AndyProwl:也感谢我!非常感谢时间和细节。【参考方案2】:

如果你愿意,你可以通过给出这个来避免调用复制构造函数:

const wchar_t* pPtr = (argc == 100) ? 0 : (pPtr = ds);

【讨论】:

以上是关于VS 2012 中的条件运算符类型转换的主要内容,如果未能解决你的问题,请参考以下文章

第三部分类型转换,运算符和条件结构

js 的书写位置——三大核心——变量——输出语法——js 的数据类型——检测数据类型——数据类型转换——检测非数字的方法——运算符——条件分支 - if——条件分支 - switch

使用Java语言深入理解程序逻辑:条件结构精讲(觉得不行的可以点赞加关注,下次再来评价)

使用Java语言深入理解程序逻辑:条件结构精讲(觉得不行的可以点赞加关注,下次再来评价)

条件运算符不能隐式转换?

将三元条件运算符转换为 if 语句?