Free Web space and hosting from ourfamily.com
Search the Web

ANSI/ISO C++ Incompatibility Page

Author:  Ronald Fischer
    Most recently modified: 2002-07-18

The  ISO C++ Standard  (referred to here as DWP) supersedes the (not always unambiguous) "de-facto" standard based on The Annotated C++ Reference 2nd ed by Ellis/Stroustrup (referred to here as ARM). While containing mostly extensions,   a few incompatibilities also exist: Constructs that used to work under ARM rules now cease to work, or have a different meaning.

This page is intended to contain a  complete list of all incompatibilities.  Nevertheless I fear that this list is far from being complete and probably contains quite a few errors. If anyone encounters an error or omission, please send a message to  coconut@unladenswallow.de.  If you report a new incompatibility not mentioned here, please include sample code to demonstrate your point.

No suggestions are given on how to port existing code to ANSI  C++. The sole intent of this page is to make experienced C++ programmers aware of what  to look for if they are writing new code. New C++ features are not discussed, for the simple reason that   excellent sites devoted to this topic already exist - for instance Beyond the ARM (http://www.corfield.org/cplusplus.phtml) and Johannes Weidl's STL tutorial (http://www.infosys.tuwien.ac.at/Research/Component/tutorial/prwmain.htm).



Contents:
Incompatibilities
Type for string literals changed
Variables declared in a for-loop are local to the loop
Inline functions have external linkage
Non-injection of friend functions into containing scope
Different rules for overload resolution
Conversion operators to const- and non-const type
New reserved names
Access to entities defined in standard library headers
Return value of default operator new
Accessability of operator delete
Using dynamic memory allocation expressions as actual parameter on function calls
Member pointers to class instances that are declared, but not defined
Other things you should be aware of
Obsolete features
No static declarations on file scope
No old-style cast
No re-adjusting of access declarations
Clarifications
Change in the naming of library headers
Changes with respect to templates

Incompatibilities

Type for string literals changed

The type of a string literal is now char const[] instead of char[], as it used to be.  This makes the following code invalid:

template <class T> void g(T* a, T* b)
                { cout << a << b; }

void g()
{
        char  a[] = "first"; // OK (allowed by DWP 8.5.2)
        g(a, "second"); // ERROR
}

Note that

extern void f(char*);
void g()
{
        f("hello world");
        char* z = "last";
}

is still allowed due DWP 4.2§2, but is considered a deprecated feature, which may be removed in a future revision of the standard. A list of deprecated features can be found  in DWP Annex 0.

Example contributed by  Valentin Bonnard.
See also DWP 2.13.4


Variables declared in a for-loop are local to the loop

A variable declared in a for-loop can only be accessed inside the loop body, acording to the DWP rules. The following program exhibits different behaviour when compiled under DWP ruling, as opposed to ARM ruling:

    int i;

    void f()
    {
        for(int i=0; i<10; i++)
        {
           if(...) break;
        }
        if(i==10) { ... } /* means local i under ARM, but ::i under DWP */
    }



 

Inline functions have external linkage

According to ARM, inline functions at file scope had internal linkage. They have external linkage now.

The following example was legal under ARM. It is illegal under DWP (but no diagnostics required):

// This is file x.cc
inline int f() { return 0; }

// This is file y.cc
inline int f() { return 1; }

Thanks to  Valentin Bonnard  for pointing this out.

Furthermore (as Sam Saariste vividly remarked), Scott Meyer explains in More Effective C++ (page 134) an interesting consequence for local statics declared within the body of an inline function: In

inline T* fetch_T() { static T* the_T = new T; return the_T; }

every translation unit had its own copy of the_T under ARM ruling, but under DWP ruling, all translation units share the same copy of the_T.

See DWP 7.1.2



 

Non-injection of friend functions into containing scope

The following example was legal under ARM. It is illegal under DWP.

class C { friend void f(); };
void g() { f(); } // f not declared

Under ARM ruling, the friend declaration would have implied an implicit extern void f(); on file scope. Under DWP, this applies only when Koenig lookup succeeds (DWP 3.4.2). For example, the following code would be legal under ARM and DWP:

class C { friend void f(C const*); };
void g() { f((C const*)0); } // fine - f is now found

Thanks to  William M. Miller  for pointing this out.
See DWP 7.3.1.2 § 3



 

Different rules for overload resolution

The rules for overload resolution changed in several areas. Examples (reviewers: please  contribute new examples):

(The following example was suggested by  Valentin Bonnard )

struct MyString {
       char& operator[] (size_t);
       operator char* ();
};
MyString s;

Under ARM ruling, the expression s[2] was legal and equivalent to s.operator[](2).  Under DWP ruling, it is implementation dependent if s[2] is legal or not: Selecting MyString::operator[] implies no conversion for s, and an integral promotion of its argument, 2, to 2u or 2ul, since size_t must be an unsigned integral type. Selecting MyString::operator char* means doing a user-defined conversion on s (converting it from MyString to char*), but  possibly also an integral promotion of 2: The builtin operator[] expects a ptrdiff_t as argument, and this must be typedefed to a signed type. If ptrdiff_t happens to be int, no promotion is necessary and none of the two alternatives fulfils the requirement over.match.best defined in DWP 13.3.3 (being strictly better-or-equal in all arguments and strictly better in at least one), hence the call is ambiguous. If ptrdiff_t is defined to be long, a promotion is necessary from 2 to 2l. In this case, the first alternative (MyString::operator[]) is better in the implied argument, s, and both alternatives are equal in the second argument, 2, hence s[2] is unambiguous and the first alternative is chosen.


Conversion operators to const- and non-const type

As Hartmut Schäfer kindly explained, there is an incompatibility regarding conversion operators.  Consider

struct Foo;
void f(Foo const&);
struct Bar
{

operator Foo&();
operator Foo const&();
};
Bar b;

Given this definitions, a call to f(b) used to be equivalent to f(b.operator Foo const& ()). This is no longer the case: According to the new rules [see DWP 13.3.1.6], the call is now equivalent to f(b.operator Foo&()).
 


New reserved names

There are additional names reserved as keywords, which can no longer be used for naming identifiers: explicit, bool, mutable, template, typeid, typename, and, or, xor, bitor, compl, bitand, and_eq, or_eq, xor_eq, not, not_eq, const_cast, dynamic_cast, reinterpret_cast, static_cast, true, false, namespace, using, throw, try, catch. For example, the following program, while legal under ARM, is illegal under DWP:

struct typename { int typeid; char name[20]; }; /* illegal: typename, typeid */
typename const* explicit(char const*); /* illegal: explicit */
typename const* implicit();

typename* try(char const* template, int mutable) /* illegal: try, template, mutable */
{
     return mutable ? explicit(template) : implicit();
}

Note that there is also a flood of "new" names, that are not designated keywords, but nevertheless are likely to be visible in namespace std due to the  inclusion of standard headers. Examples are auto_ptr and allocator (defined in <memory>), nothrow (defined in <new>), type_info (defined in <typeinfo>),  or exception and unexpected (defined in <exception>).


Access to entities defined in standard library headers

With the introduction of namespaces, entities defined in standard library headers - with the exception of operator new, operator delete and everything in the C library headers - belong now to namespace std.  This means for instance that including  iostream  is not enough to use it in the way it worked with ARM:

#include <iostream>
#include <cstdlib>
int main()
{
    cout << "hello world\n"; // ERROR: cout not known
    return EXIT_SUCCESS; // OK, comes from cstdlib
}

Instead one must either qualify the entity,

#include <iostream>
#include <cstdlib>
int main()
{
    std::cout << "hello world\n"; // OK: std::cout comes from iostream
    return EXIT_SUCCESS; // OK, comes from cstdlib
}

or use a using directive:

#include <iostream>
#include <cstdlib>
using namespace std;
int main()
{
    cout << "hello world\n"; // OK: cout comes from iostream
    return EXIT_SUCCESS; // OK, comes from cstdlib
}

or use a using declaration: (- Example contributed by Jim Hill)

#include <iostream>
#include <cstdlib>
using std::cout;
int main()
{
     cout << "hello world\n"; // OK: we said we mean std::cout
     return EXIT_SUCCESS; // OK, comes from cstdlib
}

See DWP 17.3.1.1
 



 

Return value of default operator new


When operator new is unable to allocate memory, it throws an exception. Under ARM rules, it returned 0, so the following code made sense under ARM, but not under DWP:

    if((p=new T) == 0) { ... /* never executed  with ANSI C++ */ }


Accessability of operator delete

DWP states in 5.3.4 that operator delete must be accessible at the point where operator new is invoked. This is to assure that if an exception is thrown  during construction, the object must be properly deallocated. This makes the following legal ARM code illegal under DWP:

struct S {
        static void* operator new(size_t, void*);
    private:
        static operator delete(void*,size_t)
};

void f(void* p)
{
        new (p) S; // legal with ARM, illegal with DWP
}

See  C++ Report , Sep. 1998, p. 14.



 

Using dynamic memory allocation expressions as actual parameter on function calls

The expression f(new T1, new T2) provides the danger of a memory leak, if either T1 or T2 or operator new throw an exception. One reason is that if the compiler uses the following sequence of steps to evaluate the expression,

        1: allocate memory for T1
        2: construct T1
        3: allocate memory for T2
        4: construct T2
        5: call f()

and if step 3 or 4 fail due to an exception, the standard does not enforce that the work done by steps 1 and 2 are cleaned up. See  http://www.cntc.com/resources/gotw056.html  for details.



 

Member pointers to class instances that are declared, but not defined

The effect of taking the address of a non-static member, or of a base class, of a non-POD class that is declared, but not yet  defined, is undefined behaviour under DWP rule. AFIK, this issue was not addressed at all in ARM, so you can say it was implicitly undefined in ARM and is explicitly undefined in DWP ;-)

The reason I list it here is that is used to be "common sense" that this should work and probably all old compilers and most new ones do make a sensible translation. Note also that this applies only to non-POD classes.

            struct B {};
    struct D : public B {
       int m;
       virtual ~D(); // to make D non-POD
    };
    extern D d; // declaration
    D* pd = &d; // o.k.
    int* pi = &d.m; // undefined
    B* pb = &d; // undefined
    D d; // definition

See  C++ Report , Apr. 1997.



 

And be aware of.....


The DWP contains a few obsolete features, which are carried over from "old" C++, but are not guaranteed to be supported in the next version of the standard, so you better do not use them:
 

No static declarations on file scope

Because of the introduction of namespaces, a translation unit should not declare anymore a variable at file scope as static, but use an appropirate namespace instead.



 

No old-style cast


C-style casts of the form

        (TYPEEXPRESSION)(EXPRESSION)

or C++ functional style casts of the form

      TYPENAME(EXPRESSION)

should not be used anymore.

Example contributed by Sam Saariste.


No re-adjusting of access declarations

struct  base {
   protected:
        void foo();
};

ARM style (deprecated):

struct derived: private base {
    public: base::foo; // adjust access
};

DWP style:

struct derived: private base {
    public: using base::foo; // grant access
};


A few items were clarified in the standard in  such a way that they, while not contradicting the ARM, are nevertheless  different enough to common practice in pre-DWP times that they deserve to be mentioned:
 


Change in the naming of library headers

The name of those standard library headers, which are not carried over from ANSI C, get the ".h" suffix dropped, so instead of

#include <iostream.h> // "Old" style used by many compilers

you should write now

#include <iostream> // DWP

to ensure compatibility across different implementations.


Changes with respect to templates

The DWP changed and clarified  many rules regarding template definition and instantiation. Since templates where considered an experimental feature in ARM and implementations varied heavily from vendor to vendor anyway, I don't want to spend research effort in this topic.  However, if someone sends me  a complete coding example, I will be glad to  include it here.



Author:  Ronald Fischer
If you encounter problems with this  page, please contact  armagnac@gmx.net
 

Submitted with help from ... SavvySearch: search once, find everything.