When you first compile an existing code with KAI C++ (KCC), you may be shocked at the number of error messages that it generates. In most cases, the problem is the rapid evolution of the C++ language and the fact that KAI C++ tracks this rapid evolution. In general, the designers of C++ have tried to accommodate backwards compatibility to older versions, but in some cases doing so was judged awkward or contradictory.
With KAI C++, sometimes it is sufficient
to use the command-line switch --cfront_3.0
to get the
old Cfront behavior. Usually, however, you must make minor changes to your
code to bring it up to date with important changes in the language. With
a little practice it is possible to modify codes so that they compile under
both the old and new rules. In our experience, the changes take little time,
and have the benefit of making the code suitable for more modern C++ compilers.
The rest of this document summarize the most common compatibility problems
and how to quickly fix them.
The scope of this document is limited to difficulties in migrating existing codes. New C++ language features that you might want to use in new codes are desribed elsewhere. We also recommend that you look at the ISO C++ Standard when you encounter compilation difficulties, particularly when diagnosing library problems.
If you encounter additional problems in porting your Cfront or GNU C++ code to ISO C++, or porting old STL codes, please let us know so we can add that problem to this list, allowing others to learn from your experience.
KAI C++ Version 3.3 supports both the ISO Draft Standard rules and Cfront rules. For brevity, the former will be referred to as ``ISO rules'', though it should always be remembered that the current ISO document is still a draft and subject to some change.
By default, KAI C++ parses according to the ISO rules. The
command-line switch --cfront_3.0
forces Cfront 3.0 rules.
The Cfront rules are essentially those of the Annotated Reference Manual
(ARM), except that a few obscure Cfront bugs are duplicated.
The combined standard template library and its runtime support code that ships with KAI C++ is distinctly ISO, though with some extensions that support Cfront codes.
There are many features in ISO C++, beyond what is the ARM. Most of these new features, such as run-time type information (RTTI), are upward compatible with Cfront. This section concerns the few features that are not upward compatible.
KAI C++ tries hard to be permissive when
it comes to language differences between ISO and Cfront. As long as there
is no fundamental incompatibility, it tends to allow Cfront code, even in
ISO mode. Such permissiveness can be turned off with the command-line switch
--strict
.
ISO and Cfront rules differ on resolution of overloaded functions. Cfront's rules for overload resolution allowed for some fine shades of overload resolution, at the expense of complicated rules. The simpler (and saner) ISO rules disallow some of these fine shades. The most common problem that users encounter is demonstrated by program overload.C. When compiled by KAI C++ (without the cfront_3.0 switch), KAI C++ will object:
"overload.C", line 12: error: more than one operator "[]" matches these operands: built-in operator "pointer-to-object[integer]" function "Vec::operator[](unsigned int)" operand types are: Vec [ int ] v[0] = 1; ^
By the ISO rules, KAI C++ finds two matches because there are two possible "conversion sequences", and each involves a different parameter. The two matches are described below with references to the relevant sections of the ISO Working Paper.
v
), apply the user-defined
conversion: sequence Vec --> int*
. This conversion
allows built-in operator pointer[integer]
to match.
0
), apply the standard
conversion sequence int --> unsigned int
. This conversion
allows function Vec::operator[]
to match.
Under the ARM (Cfront) rules, match B wins since it involves only standard conversions. However, The ISO rules are different (13.3.3). For a match to win, it must have "better" conversion sequences for each parameter than the losing match. So we have:
Thus neither match is better than the other and there is an ambiguity.
So this leaves the question of how to change the code to make it acceptable
to ISO C++ compilers. The probably intent was to have match B win. To do
this, remove the need for the conversion (int --> unsigned int
)
by making the actual parameter unsigned
. E.g.:
v[0u] = 1;
The overload problem is not specific to the operators in the example.
However, there are so many Cfront codes that have problems only with operator[]
that KAI C++ comes with an option to deal with
it while otherwise in ISO mode. The option --special_subscript_cost
causes the overload resolution algorithm to give special weighting to operator[]
and avoid the aforementioned problems. For example,
KCC --special_subscript_cost overload.C
will compile overload.C.
The ISO rules add some new keywords. Even in Cfront mode, KAI C++ recognizes these keywords:
dynamic_cast catch const_cast mutable namespace reinterpret_cast static_cast throw try typeid using wchar_t
This should not be a problem unless an old code uses one of these keywords as an identifier. These keywords are now recognized by a wide variety of C++ compilers, it is worth your time to change obsolescent uses of these as identifiers.
Keywords listed below are given special treatment, because they are very recent or their new usage would break common practice in old codes.
#define
or typedef
for
these keywords, particularly for false
and true
.
To enable the boolean type in Cfront mode, use the command-line switch
--bool
.
explicit
is not recognized in Cfront
mode. To enable it, use the option --explicit
.
typename
is not recognized in Cfront
mode. To enable it, use the option --typename
. See
also the discussion of --no_implicit_typename
.
ISO recently introduced a keyword typename
. Its use
is required inside templates for certain contexts by the ISO rules, as demonstrated
by typename.C. However, it is so recent that very
few codes use it. Therefore, by default, KAI C++
infers where the keyword typename
should have been used.
To turn off this inference, use the option:
--no_implicit_typename
The option --strict
also turns off the inference.
If you want strict ISO except for typename
, use the
options --strict --implicit_typename
.
When compiling Cfront codes that use template definitions not found
in the #include
d header files, turn on the
implicit inclusion
feature with the command-line option --implicit_include
.
This non-standard feature
complicates the production of accurate dependence information and
the diagnosis of template declaration errors, and so is off by default.
Consider the loop:
for( int i=0; i<n; i++ ) { ... }
The ISO rules limit the scope of i
to the loop; the
Cfront rules treat i
as though it were declared just
before the loop. The most common problem is that the new rules break code
that use i
outside the loop. For instance, a linear
search that looks at i
after the loop exits. The fix
is simple: move the declaration and initialization to outside the loop.
int i=0; for( ; i<n; i++ ) { ... }
You should move both the declaration and initialization. Below is an example of what could go wrong if just the declaration is moved.
T i; // Default constructor. for( i=0; i<n; i++ ) { // Assignment to i ... }
Now the default constructor T::T()
and T::operator=
are used for i
, instead of just the one-argument constructor
that was used in the original loop.
The ISO rules can also change how a program behaves, though this is unlikely for real programs. Program for-scope.C demonstrates a contrived program whose behavior depends upon ISO versus Cfront rules. KAI C++ warns about such a change in behavior change with a message such as shown below.
"for-scope.C", line 14: warning: reference is to variable "i" (declared at line 7) -- under old for-init scoping rules it would have been variable "i" (declared at line 11) switch( i ) { ^
This is the most insidious difference between ISO and Cfront rules. The problem occurs when binding a reference to a pointer to a qualified type to an unqualified pointer. For example, the behavior of program bind-ref.C differs depending upon which rules are used.
One way to look for this problem is to use the following command-line switch:
--diag_warning=340
The switch causes KAI C++ to report situations where it is generating a temporary for a reference.
The example used a reference for a formal parameter. The problem can also manifest itself with other forms of references, notably class members. In the case of a class member declared as a reference, the generated temporary will almost surely have too short a lifetime, and leave the reference dangling.
C++ compilers can generate anonymous temporary objects. Cfront and ISO rules differ on when such objects are destroyed. Cfront let such objects live until the end of the enclosing block; ISO destroys such objects at the end of the containing "full expression". For instance, program lifetime.C behaves differently depending upon which rules are used.
We have not run into real codes for which lifetimes of temporaries is a problem. The reason is probably that programmers have long since steered clear of this problem, because the ARM gave implementations discretion on when temporaries could be destroyed. Thus implementations of C++ based on the ARM thus varied from giving temporaries long lifetimes (as with Cfront) to extremely short lifetimes (as with the original GNU C++). The ISO rules fall between the extremes, and so are unlikely to cause problems with codes that compiled under both Cfront and GNU C++.
If you are using commercial libraries such as Rogue Wave's, try reinstalling the libraries with KAI C++. The installation process for many commercial libraries automatically figures out how good the KAI C++ compiler and libraries are, and install the commercial library to match.
The C++ class library shipped with KAI C++ is a modified form of the Modena C++ Standard Library. This library tracks the ISO Draft Standard. KAI's modifications enable older codes to run with fewer modifications.
The biggest change is that the ISO library puts most library identifiers
in the namespace std
. To minimize transition difficulties,
the include files for
KAI C++
adopt the convention
that if header file <foo>
defines a public symbol
bar
, then the header file <foo.h>
does the same, but exports bar
to the global namespace
too.
For example, the header file <iostream>
defines
the class std::ostream.
Just having this header file
would breaks Cfront codes in two ways:
<iostream.h>
To solve these problems KAI C++ supplies
another header file <iostream.h>
. This header
file includes <iostream>
, and then exports std::ostream
to the global namespace.
There is one exception to the rule. Because many codes expect that <string.h>
is the header already defined by the ISO C standard, the .h file corresponding
to C++ header file <string>
is called <bstring.h>
,
not <string.h>
. The name <bstring.h>
was chosen to follow the convention of some other existing C++ implementations.
If you use <complex.h>
, the only way to make
your code compatible with the Cfront and ISO libraries is to make judicious
use of typedefs
.
For example, we patched in the following for an old copy of a Rogue Wave library:
#elif defined(__KCC) # include <complex.h> typedef complexDComplex; typedef DComplex (*CmathFunTy)(DComplex&); typedef double (*CmathFunTy2)(DComplex&);
Of course, if you have a recent copy, reinstalling it with KAI C++ should fix the problem.
The Cfront <complex.h>
defines a class complex
for double-precision complex numbers. The KAI C++
versions follows the ISO draft -- it defines a template class complex<T>
,
where parameter type T
can be float, double, or long
double. To add to the confusion, the old GNU library called it class Complex
.
We advise using a typedef such as DComplex
for complex
numbers, and conditionally defining it:
#if defined(__KCC) #include <complex.h> typedef complex<double> DComplex; #else #include <complex.h> typedef complex DComplex; #endif
The Cfront header <complex.h>
has many functions
that pass complex numbers by value. In contrast, the KAI
C++ version (and some others versions such as GNU) pass complex numbers
by const
reference. Normally, the difference is not
noticeable. However, we ran across one code that took the address of member
functions in class complex
. For instance, it tried to
pass &complex::sqrt
to another function. For the
Cfront library, such an address has type:
complex complex::*( complex );
For the KAI C++ library, such an address has type:
complexcomplex ::*( const complex & );
The two pointer-to-member types are incompatible. The most reasonable
way to save the code is to use typedefs
for pointer-to-member.
For example, we were able to revive an ancient Rogue Wave code by adding
the following to the Rogue Wave complex.h
.
#if defined(__KCC) #include <complex.h> typedef DComplex (*CmathFunTy)(DComplex&); typedef double (*CmathFunTy2)(DComplex&); #else #include <complex.h> #endif
Reinstalling the Rogue Wave library is probably simpler in most cases. The hacks above are only for old frozen libraries.
KAI C++ Version 3.0 dropped support for <generic.h>
.
This header file is fossil from the age before templates. You have to read
Stroustrup's original description of C++ to even find out about it. If you
really want a version of it for the current version of KAI
C++, ask us, and you can make your own from this
version from GNU C++.
There are numerous minor incompatibilities between Cfront's iostream and ISO's iostream. These may require minor repairs to make old codes comply with the ISO iostream.
In our experience, the biggest problem are codes that contain forward
declarations of classes declared in <iostream>
,
such as shown below.
class ios; class istream; class ostream; class iostream;
If any of these forward declarations occur before <iostream.h>
is included, KAI C++ will report the following
sort of error:
include/ostream.h: error: "ostream" has already been declared in the current scope using std::ostream; ^
The reason is that <ostream.h>
defines std::ostream
and then exports ostream to the global namespace. But the earlier definition
of ostream, is already there! Furthermore, the ISO definitions of these types
are not classes, but typedefs to template instances.
There are two ways to fix the problem in existing code.
The preferred work-around is to replace the forward declarations with:
#include <iosfwd.h>
This will do the necessary forward declarations. The pure ISO way would
be to omit the .h
and use #include <iosfwd>
but that will not save old code because it retains the definitions inside
the namespace std::
. Including <iosfwd.h>
has similar effect, except that the definitions are exported to the global
namespace so that pre-namespace code works. One nice property of fixing
the code this way is that you can make it run with old C++ compilers by
creating your own header file iosfwd.h
and putting it
in the old compiler's search path for include files.
A similar problem exists for many other I/O classes such as fstream, streambuf, etc.
In all cases, replacing old forward declarations with inclusion of
<iosfwd.h>
should solve the problem.
The method streambuf::sync
should be replaced with
streambuf::pubsync
. In the ISO C++ library, streambuf
is a typedef
for the following template instantiation.
std::basic_streambuf<char, std::ios_traits<char> >::sync
Calls to the method streambuf::stossc()
should be
replaced by calls streambuf::sbumpc()
, with the result
cast to void
. For instance, change rdbuf->stossc
to (void)rdbuf->sbumpc()
.
The Cfront versions of ostream
and istream
have default constructors with protected access. The idiom was
to use the default constructor, and then call method init()
.
Below is an example of the old style for a class foostream
derived from ostream
.
foostream::foostream( ostream& s ) : ostream() { ios::init(s.rdbuf()); }
The ISO specification has no such constructor ostream()
.
Instead, the streambuf
should be passed to the constructor
for ostream
. Below is an ISO version of the aforementioned
example.
foostream::foostream( ostream& s ) : ios( s.rdbuf() ), ostream( s.rdbuf() ) { ios::init(str.rdbuf()); }
The following was allowed by Cfront's library, but would seem to not be allowed by the April 1995 public copy of the ISO Draft.
ofstream f; f.open( "foo", ios::open_mode::out );
The problem is that using ``out'' alone is ambiguous -- it does not indicate
whether the file is to be truncated or appended. The library shipped with
KAI C++ allows the use of ios::open_mode::out
and takes it to be the equivalent of w
for the UNIX
fopen, so as to yield the old Cfront behavior.
However, you may want to steer clear of the ambiguity by passing ios::open_mode::trunc
as part of the flags as shown below.
ofstream f; f.open( "foo", ios::open_mode::out|ios::open_mode::trunc );
Cfront had a include file <math.h> corresponding to the same in
C. KAI C++ supplies a similar <math.h>
,
so codes using it should compile without difficulty.
However, you may eventually want to migrate to using the ISO header <cmath>
.
Though the migration is fairly painless, you should be ready for the following
surprise. The header <cmath>
introduces overloaded
prototypes for math functions such as sqrt
. These prototypes
declare them for float
and long double
,
and are invaluable for writing templates that work for all precisions. However,
these extra prototypes add ambiguity to formerly unambiguous calls. For
example, in the code below:
#include <cmath> double golden_ratio() {return (sqrt(5)+1)/2;}
the call sqrt(5)
cannot be resolved because the (int)
5
could be converted to a float
, double
,
or long double
. When switching from <math.h>
to <cmath>
, you need to add casts to disambiguate
such calls. For instance, the aforementioned example can be fixed by replacing
the argument 5
with 5.
or (double)5
.
Weaknesses in the G++ implementation of templates cause programmers using G++ to adopt a somewhat idiosyncratic style that may cause problems with KAI C++. There are also parts of the GCC library that may not match the current ISO Working paper. The list below of problems with compiling G++ codes with KAI C++ is by no means exhaustive. We appreciate any feedback on this issue.
Problems with G++'s implementation of templates cause some programmers
to resort to writing all member functions of template classes inside the
template declaration for the class, as shown in excessive-inline.C
. The ISO C++ rules treat all such member functions as implicitly declared
inline
. At optimization level +K1
and higher, KAI C++ relentlessly carries out
inlining even for very complicated functions. Unless there is a lot of unreachable
code involved, the result will be monstrous code bloat.
Use the command-line switch --inline_keyword_space_time=8
to clamp down on excessive inlining. The value of 8 is a good initial guess
for most cases. If you want to tweak it, read Section 3.2.3 (Automatic Inlining)
of the KAI C++ User's Guide for the meaning
of the switch.
Some versions of GNU C++ give all template instances internal linkage,
even for entities that should have external linkage. We previously advised
using the KCC option -tlocal
, but this option has been discontinued
because it cannot yield correct results with the KCC draft-standard
library. Automatic template instantiation should give correct results unless
the program relied on GNU C++'s erroneous template model.
ios::open_mode::bin[ary]
The GNU iostreams library defines a bitmask ios::open_mode::bin
,
whereas the ISO specification names it ios::open_mode::binary
.
You might think that STL is new enough that no migration problems arise from using it. Alas, STL is evolving with the language.
The code fragment and error message in file vector.C
demonstrate a subtle hassle of the current STL specification. The problem
arises from the fact that the following two constructors for class vector
look very similar to the compiler.
explicit vector (size_type n, const T& value = T ()); template<class InputIterator> vector (InputIterator first, InputIterator last);
Notice that the latter constructor matches exactly any pair of arguments that are of the same type. If your intent is the first constructor, be very careful in typing the arguments, otherwise the second constructor may be a better match.
For example, the declaration vector<int>(10,1)
does not mean ``a vector of 10 integers initialized to 1''. It means a vector
initialized using int
as an iterator, starting at 10
and ending at 1. Unfortunately, int does not have the properties required
of an iterator, and the compilation fails in a somewhat cryptic way deep
inside the template instantiations. Writing the 10
as
10u
fixes the problem, since it makes a better match
(integral promotion rather than conversion) with the intended constructor.
The old STL had global functions iterator_category
, distance_type
,
and value_type
that enabled a circuitous style of finding out
about types related to iterators. The draft-standard style uses a more direct style based on
the class iterator_traits
.
The template class stack now takes the element type, not the container type as its argument. See here for more details.
KCC --no_exceptions
.
--implicit_include
.
template
is now required when referring to member template typename
s.
getline
in <string> no longer counts characters.
valarray<T>
no longer has implicit conversions
to T*
and const T*
.
KCC -o libX.a x1.o
'' removes libX.a and builds a
new one containing only x1.o.
std::allocator<T>::deallocator
is now taken seriously.
Specifying an incorrect value may corrupt memory.
this
is no longer allowed, even in Cfront modes.
This fossil feature from Cfront 1.2 disappeared in Cfront 2.0,
and so is unlikely to affect codes outside museums.
valarray
now conforms to the standard,
which makes it much less useful.