We've recently begun upgrading our code from Visual Studio 2005 to a newer version, and we ran into the following doozy.

Imagine that you have classes with the following relationships:

// Lightweight reference to data owned by another object.
struct Light { Light(); Light(Light const&); };

// Owns data freed on release. Constructor copies data. Conversion to Light does not copy.
struct Heavy { Heavy(); Heavy(Heavy const&); Heavy(Light const&); operator Light() const; };

In a reasonable world, you would then expect the following code, using the C++ conditional operator:

Light DataOrDefault(Heavy const* h)
{ return h ? *h : Light(); } // BAD BAD BAD!

... to work like this code using a simple if statement:

Light DataOrDefault(Heavy const* h)
{ if (h) return *h; return Light(); } // OK

... or like this code, using a template:

template <class T> inline T Cond(bool expr, T a, T b)
{ return expr ? a : b; }

Light DataOrDefault(Heavy const* h)
{ return Cond<Light>(h, *h, Light()); } // OK

But it does not. Do you know how it works? This is how it works.

Light DataOrDefault(Heavy const* h)
{
// Actual behavior of: h ? *h : Light()
Heavy t;
if (h)
t = *h;
else
t = Light();
return t;
}

Yes. The compiler decides that the common type of the two expressions is Heavy. A temporary Heavy object is then constructed to hold this result. A copy of the data is constructed; the copy is assigned to Light; and then it is promptly destroyed, so that Light now points to invalid data.

In GCC, this produces an error. But in Visual Studio, there is no warning, even with -Wall:



Which of the two behaviors is standards-compliant, I do not know. (Added: In comments, Lee Killough argues persuasively that GCC is compliant; VC++ is not.)

On the MSDN page for the conditional operator, you can find this notice:


The compiler authors are themselves warning you this behavior is bad, and telling you not to use this operator.

Let it be clear: my concern is not making the ambiguous code work. It's to prevent it from compiling in the first place. It should at least cause a warning, so that ambiguities can be found and rooted out, and so that new ambiguities will not be implemented.

I can visually check the code, but can I write it in a way that, if it was unsafe, it would produce a warning? What if there's a conversion I didn't think of? The code would just compile silently.

Workaround?

Due to namespace issues, I dislike macros, but this lambda-based macro is the best I've come up with:

#define If(EXPR, T, A, B) \
    (([&]()> T { if (EXPR) return (A); return (B); })())

You might be surprised at the performance. Using Visual Studio 2010, in the Debug configuration, it's only slightly slower than the ternary operator. In the Release version, it is several times faster. (And I did attempt to make sure that the useful effect of the code is not being optimized away.)

Further discussion

Some argue the real danger is the implicit conversions between Heavy and Light. I find this not to be the case. The conversions are safe and intuitive in other situations. The following, for example, will not build:

template <class T> inline T Cond(bool expr, T a, T b)
{ return expr ? a : b; }

Light DataOrDefault(Heavy const* h)
{ return Cond(h, *h, Light()); } // Compiler error: T is ambiguous

Here, the compiler observes that the template type T could be either Light or Heavy. It refuses to make a potentially unsafe and incorrect assumption, and requires the type to be specified:

Light DataOrDefault(Heavy const* h)
{ return Cond<Light>(h, *h, Light()); } // OK

In the case of the ternary operator, MSVC simply goes ahead with one of the possible paths; often one that is very counter-intuitive. There is no error, no warning.

If we just swap the operands as follows:

Light DataOrDefault(Heavy const* h)
{ return !h ? Light() : *h; }

... the compiler decides the proper type to use is Light, and the code will work fine.


2015-06-05: Ongoing edits in response to comments.
2015-06-06: Used tohtml.com/cpp for syntax highlighting instead of images.