Migrating to ISO C++



Introduction

Compiling RogueWave Libraries   

Report Additional Issues

Support for Cfront Codes   

Compiling old STL

KAI C++ Home

Language Differences

Compiling GNU C++ codes   

KAI Home

Library Differences

Compiling old  KCC codes

Contact KAI++


 

Introduction -- Keeping Up with the Evolution of C++

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.


Report Additional Issues

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.


Support for Cfront and ISO Codes

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.


Dealing with Language Differences

Basic Approach

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.

Overload resolution

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.

A: (13.3.3.1.2)
To parameter 0 (which is v), apply the user-defined conversion: sequence Vec --> int*. This conversion allows built-in operator pointer[integer] to match.
B: (13.3.3.1.1)
To parameter 1 (which is 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.

New keywords

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.

bool, false, true

These keywords are not recognized in Cfront mode, because many old codes have #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

The keyword explicit is not recognized in Cfront mode. To enable it, use the option --explicit.

typename

The keyword typename is not recognized in Cfront mode. To enable it, use the option --typename. See also the discussion of --no_implicit_typename .

Implicit use of keyword 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.

Turning on implicit inclusion

When compiling Cfront codes that use template definitions not found in the #included 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.

Scope of for loops

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 ) {
                      ^

Binding references to temporaries

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.

Lifetime of temporaries

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++.


Library Differences

Basic Approach

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.

<foo> vs. <foo.h>

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:

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.

<complex.h>

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 complex DComplex;
               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.

Type complex is now a template

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

Pass by reference vs. value

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:

        complex complex::*( 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.

<generic.h>

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++.

<iostream.h>

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.

Forward declaration of ios, istream, ostream, etc.

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.

Nonstandard features enabled by -DKAI_NONSTD_IOSTREAM

Non-ISO features of the library are by default turned off. To turn them on, you must define certain preprocessor symbols. There is a table of such symbols in the User Guide. In partifular, some old Cfront codes may require features enabled by putting -DKAI_NONSTD_IOSTREAM on the KAI C++ command line.

Method streambuf::sync replaced

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

Method streambuf::stossc replaced

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().

No default constructor ostream() or istream()

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());
        }

File mode ios::open_mode::out needs trunc or append

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 );

<math.h> vs. <cmath>

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.


Compiling GNU C++ codes.

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.

Clamping Down on Excessive inlining

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.

Linkage of template instances

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.

Bitmask 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.


 

Compiling old STL codes.

You might think that STL is new enough that no migration problems arise from using it. Alas, STL is evolving with the language.

Member overload confusion

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.

Iterator type query functions

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.

New arguments for template class stack

The template class stack now takes the element type, not the container type as its argument. See here for more details.


Compiling old KCC codes.

Here are issues for application programs being migrated from earlier versions of KCC. In all cases KCC's new behavior either adheres more closely to the final draft standard, or offers improved efficiency, correctness, or flexibility.

Migrating from KCC 3.2

Here are issues for application programs being migrated from KCC 3.2 to KCC 3.3.

Migrating from KCC 3.3

The big issues for migrating from KCC 3.3 to KCC 3.4 are the ISO rules for name lookup. If your code breaks, be sure that you understand the first two items listed below before reporting a bug to KAI.

Copyright © 1995-1999 by Kuck & Associates, Inc. All rights reserved.
, KAI C++ and KAI are trademarks of Kuck & Associates, Inc.