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' 的转换
第二个和第三个操作数不是同一类型的左值,因此条件表达式的结果是从所选操作数构造的右值。在任何情况下,您都不应该期望结果与DummyString
和ds
相同。
【参考方案1】:
C++11 标准第 5.16/3 段规定:
[...] 如果第二个和第三个操作数有不同的类型并且有(可能是 cv 限定的)类 类型,或者如果两者都是相同值类别和相同类型的左值,除了 cv-qualification,an 尝试将这些操作数中的每一个转换为另一个的类型。 [...]
还有:
[...] 如果两者都可以 转换,或者可以转换但转换不明确,程序格式错误。如果两者都没有 可以转换,操作数保持不变,进一步检查如下所述。 如果恰好可以进行一次转换,则该转换将应用于所选操作数和转换后的 本节其余部分使用操作数代替原始操作数。
在这种情况下,只有从 int
到 DummyString
的转换是可能的,因为在另一个方向上,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),这必须在编译时确定。如果a
和b
都是prvalues,或者如果a
和b
都是glvalues,我们就设置好了。但是,如果一个是glvalue,另一个是prvalue,我们需要从前者中创建一个prvalue——所以编译器构造一个临时的,最终条件表达式的值类别是prvalue
@AndyProwl:也感谢我!非常感谢时间和细节。【参考方案2】:
如果你愿意,你可以通过给出这个来避免调用复制构造函数:
const wchar_t* pPtr = (argc == 100) ? 0 : (pPtr = ds);
【讨论】:
以上是关于VS 2012 中的条件运算符类型转换的主要内容,如果未能解决你的问题,请参考以下文章
js 的书写位置——三大核心——变量——输出语法——js 的数据类型——检测数据类型——数据类型转换——检测非数字的方法——运算符——条件分支 - if——条件分支 - switch
使用Java语言深入理解程序逻辑:条件结构精讲(觉得不行的可以点赞加关注,下次再来评价)