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).
IncompatibilitiesType for string literals changedOther things you should be aware of
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 definedObsolete featuresNo static declarations on file scopeClarifications
No old-style cast
No re-adjusting of access declarationsChange in the naming of library headers
Changes with respect to templates
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
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 */
}
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
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
(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.
struct Foo;
void f(Foo const&);
struct Bar
{
operator Foo&();};
operator Foo const&();
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&()).
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
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++ */ }
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.
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.
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.
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:
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:
#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.
Submitted with help from ... SavvySearch: search once, find everything.