Jon*_*ter 8 c++ visual-c++ c++11 visual-studio-2012
我目前正在将一个相当大的项目从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中,上面的条件运算符将导致operator const wchar_t*()调用该方法并且ds不会触发断言.也就是说,它会隐含地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*,何时可以轻松地在原始对象上执行转换?
我很想得到开悟!:)
And*_*owl 13
C++ 11标准第5.16/3段规定:
[...]如果第二个和第三个操作数具有不同的类型并且具有(可能是cv限定的)类类型,或者如果两者都是相同值类别的glvalues和除cv-qualification之外的相同类型,则尝试将每个操作数转换为另一个操作数的类型.[...]
然后:
[...]如果两者都可以转换,或者一个可以转换,但转换含糊不清,则程序格式错误.如果两者都不能被转换,则操作数保持不变并且如下所述执行进一步检查. 如果只能进行一次转换,则将该转换应用于所选操作数,并使用转换后的操作数代替本节其余部分的原始操作数.
在这种情况下,只有转换为intto DummyString是可能的,因为在另一个方向a const wchar_t*是我们可以去的 - 没有从a const wchar_t*到a的标准隐式转换int.
这就是为什么编译器会抱怨你删除转换构造函数的原因int:如果那个构造函数不存在,按照上面的段落,程序将是格式错误的(在任何一个方向都不存在转换).
因此,第二操作数(第一选择)被认为是DummyString(0).
但是,第二个操作数可以转换为a的DummyString事实并不意味着将完全评估第二个操作数.这取决于条件,并且条件求值为false(除非您将100个参数传递给命令行),这解释了为什么您没有看到对该构造函数的调用.根据第5.16/1段:
条件表达式从右到左分组.第一个表达式在上下文中转换为bool(第4条).它被评估,如果是,则条件表达式的结果是第二个表达式的值,否则是第三个表达式的值.仅评估第二和第三表达式中的一个.[...]
但是为什么你的断言失败呢?
为什么编译器会创建一个副本
ds然后将其转换为wchar_t*,当它可以轻松地在原始对象上执行转换时?
那么,这是由于第5.16/4-5段:
如果第二个和第三个操作数是相同值类别的glvalues并且具有相同的类型,[...]
否则,结果是prvalue.如果第二个和第三个操作数不具有相同的类型,并且具有(可能是cv限定的)类类型,则使用重载决策来确定要应用于操作数的转换(如果有)(13.3.1.2,13.6) .
0不是glvalue,因此条件的结果将是一个prvalue.这意味着条件运算符的条件运算符的评估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段.