Technote: Improved Conformance to ANSI C++

As a result of improved C++ conformance, some constructs that used to compile will now give errors. Each error message that may result from some of these changes is flagged by "(new behavior; please see help)" at the end of the message. This doesn't mean that the error message has a new meaning; it means that the compiler may be diagnosing an error which it previously did not.

The errors affected are:

C2434 C2440 C2446 C2553 C2585 C2592
C2664 C2665 C2666 C2667 C2668 C2674
C2675 C2676 C2677 C2678 C2679

Overload Resolution Changes

Visual C++ overload resolution rules have been brought more in line with those specified in the draft C++ standard. This will allow some things to work that previously did not, but it also will result in ambiguities in code that compiled cleanly with earlier versions of Microsoft Visual C++.

Dereferencing a Reference is an Identity Conversion

Previous versions of Microsoft Visual C++ considered dereferencing a reference or binding to a reference to be a worse conversion than an exact match. These conversions (listed in the Annotated C++ Reference Manual as trivial conversions) are now considered to be identity.

For 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

Previous versions of Visual C++ selected the user-defined conversion to call based on the conversion required to get from the result type of the UDC to the required type and ignored the conversion required for the 'this' pointer of the UDC. User-defined conversions are now selected based on the quality of match on the 'this' pointer. As a Microsoft extension (not currently flagged by /Za), the quality of the conversion from the UDC result to the target type is used as a tie-breaker in the event that the 'this' pointers match.

This change of behavior will catch some bad errors that previously had not been detected. For 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
}

The error in this case is that the user-defined conversion is ambiguous. This is because the conversion for the this-pointer is identical for both UDCs, and the result of the UDCs has an identical conversion. The following example (added to the previous one) illustrates 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, the error occurs because neither UDC can be called—the D object is const, but neither UDC takes a const this pointer.

The way to correct both these problems is to rewrite conversion #1 as follows:

operator C () const { return myC; }  // conversion #1a

or:

operator const C& () const { return myC; }  // conversion #1b

Both these conversions take a const this pointer, so they can be called for const objects. In addition, conversion #1b returns a reference to a const object, avoiding copy construction, but still ensuring that the original object will not be modified.

An additional, more subtle effect of this change is that user-defined conversions can silently change behavior. For 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
}