Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Conditional operator type conversion in VS 2012

I'm currently converting a rather large project from VS 2008 to 2012, and have run into a problem with a change in the way conditional operator type conversion seems to be performed.

Let me start by saying that I accept the semantics of the conditional operator are somewhat complicated and realise what the code was doing originally is probably not correct, but I'm really confused by what happens now in VS 2012 and I was wondering if anyone can explain exactly why it does what it does.

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;
}

In VS 2008, the conditional operator above would result in the operator const wchar_t*() method being called on ds and the assert would not fire. That is, it would implicitly cast ds to a const wchar_t*.

In VS 2012, the conditional operator results in the following behaviour:

  • A temporary DummyString is constructed via the copy constructor
  • The cast to const wchar_t* is then performed on that temporary copy

This results in pPtr being left pointing to a destroyed object, and the assert of course fires.

Now if I remove the DummyString(int) constructor from the class, the code fails to compile in VS2012 (no conversion from 'DummyString' to 'int') so clearly the 0 in the conditional is causing the expression to be evaluated as an int rather than a pointer.

But in that case, why isn't the DummyString(int) constructor being called to convert the 0 to a DummyString? And why does the compiler create a copy of ds and then cast that to wchar_t*, when it could just as easily perform the cast on the original object?

I would love to be enlightened! :)

like image 342
Jonathan Potter Avatar asked May 29 '13 08:05

Jonathan Potter


1 Answers

Paragraph 5.16/3 of the C++11 Standard specifies that:

[...] if the second and third operand have different types and either has (possibly cv-qualified) class type, or if both are glvalues of the same value category and the same type except for cv-qualification, an attempt is made to convert each of those operands to the type of the other. [...]

And that:

[...] If both can be converted, or one can be converted but the conversion is ambiguous, the program is ill-formed. If neither can be converted, the operands are left unchanged and further checking is performed as described below. If exactly one conversion is possible, that conversion is applied to the chosen operand and the converted operand is used in place of the original operand for the remainder of this section.

In this case, only the conversion from int to DummyString is possible, because in the other direction a const wchar_t* is as far as we can go - there is no standard implicit conversion from a const wchar_t* to an int.

This is why the compiler complains if you remove the converting constructor that takes an int: if that constructor did not exist, per the above paragraph, the program would be ill-formed (no conversion would exist in either direction).

Therefore, the second operand (the first choice) is considered to be DummyString(0).

However, the fact that the second operand can be converted to a DummyString does not mean that the second operand will be evaluated at all. That depends on the condition, and the condition evaluates to false (unless you pass 100 arguments to the command line), which explains why you are not seeing a call to that constructor. Per paragraph 5.16/1:

Conditional expressions group right-to-left. The first expression is contextually converted to bool (Clause 4). It is evaluated and if it is true, the result of the conditional expression is the value of the second expression, otherwise that of the third expression. Only one of the second and third expressions is evaluated. [...]

But why does your assertion fail then?

And why does the compiler create a copy of ds and then cast that to wchar_t*, when it could just as easily perform the cast on the original object?

Well, this is due to paragraphs 5.16/4-5:

If the second and third operands are glvalues of the same value category and have the same type, [...]

Otherwise, the result is a prvalue. If the second and third operands do not have the same type, and either has (possibly cv-qualified) class type, overload resolution is used to determine the conversions (if any) to be applied to the operands (13.3.1.2, 13.6).

0 is not a glvalue, so the result of the conditional will be a prvalue. This means that the evaluation of the conditional operator when the condition is false will end up constructing a temporary from ds, which is the behavior you observe.

This is mandated by paragraph 5.16/6, which says:

Lvalue-to-rvalue (4.1), array-to-pointer (4.2), and function-to-pointer (4.3) standard conversions are performed on the second and third operands. After those conversions, one of the following shall hold:

— The second and third operands have the same type; the result is of that type. If the operands have class type, the result is a prvalue temporary of the result type, which is copy-initialized from either the second operand or the third operand depending on the value of the first operand. [...]

The condition `"the second and third operand have the same type" holds, because the operands are now being considered after the conversion described in 5.16/3 (see the beginning of this answer).

To fix your problem, you could perform an explicit cast on the second argument:

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

Since a standard conversion from 0 to a pointer type exists and results in a null pointer - see paragraph 4.10/1.

like image 79
Andy Prowl Avatar answered Oct 16 '22 13:10

Andy Prowl