Because of improved C++ conformance, some constructs that compiled in older versions of Visual C++ now produce errors. The errors affected are:
C2434 | C2440 | C2446 | C2553 | C2585 | C2592 |
C2664 | C2665 | C2666 | C2667 | C2668 | C2674 |
C2675 | C2676 | C2677 | C2678 | C2679 |
Visual C++ versions 4.0 and later bring overload resolution rules closer to the C++ standard. This allows some code to work that did not work previously, but can result in ambiguities in code that compiled cleanly with earlier versions of Visual C++.
Dereferencing a Reference is an Identity Conversion
Versions of Microsoft Visual C++ prior to 4.0 did not consider dereferencing a reference, or binding to a reference, to be an exact match. Visual C++ versions 4.0 and later considered the conversions trivial, as specified by the Annotated C++ Reference Manual.
Example
void foo(const int); void foo(int&); void bar(void) { int i; int &ri = i; foo(i); // Accepted by VC2.0, VC1.x, rejected by VC4.0, error C2668 foo(ri); // Accepted by VC2.0, VC1.x, rejected by VC4.0, error C2668 }
User-Defined Conversions (UDCs) are Selected by 'this' Pointer Types
Versions of Visual C++ prior to 4.0 selected a user-defined conversion to call by determining the required conversion from the UDC result type to the required type, ignoring the conversion required for the this pointer of the UDC. Later versions select a UDC based on the quality of match on the this pointer. If two conversions have identical this pointers, a Microsoft extension uses the quality of the conversion from the UDC result to the target type to determine the UDC. (This extension is not disabled by /Za.)
The new behavior catches some errors not previously detected.
Example
struct C { // some class }; struct D { // A class which wraps a C C myC; // Two user-defined conversion functions, one for // using myC, and the other for modifying it operator C () { return myC; } // conversion #1 operator C& () { return myC; } // conversion #2 }; // A function that takes a C void func1(C); // A function that has a D void func2() { D aD; func1(D); // Error diagnosed in VC4.0: error C2664 }
In this example, the user-defined conversion is ambiguous because the conversions for the this pointers are identical and the the UDC results have identical conversions. The following code (which follows the previous code) shows another error:
// A function that wants to modify a C void func3(C&); // Some function that gets a D& void func4(const D& aD) { func3(aD); // Error diagnosed in VC4.0: error C2664 }
This time, neither UDC can be called. The D
object is const, but neither UDC takes a const this pointer.
To correct both these problems, rewrite conversion #1 as follows:
operator C () const { return myC; } // conversion #1a
or:
operator const C& () const { return myC; } // conversion #1b
Both conversions take a const this pointer and can be called for const objects. Conversion #1b returns a reference to a const object, avoiding copy construction, but still ensuring that the original object cannot be modified.
An additional, more subtle effect of this change is that user-defined conversions can silently change behavior:
Example
void foo(int); struct C { operator int () const; operator long (); }; void main(void) { C aC; const C acC; foo(acC); // calls "operator int() const" foo(aC); // calls "operator int() const" in VC2.0, // but "operator long()" in VC4.0 }