JonHoyle.com Mirror of MacCompanion
http://www.maccompanion.com/archives/March2007/Columns/AccordingtoHoyle.htm

Blog Bazaar Forum Index Services Info Companions

 

Buy MacScan

Contact Us | ©1996-2007 MPN LLC.

Who links to macCompanion.com?

According to Hoyle...

ANSI/ISO C++ 2009: Changes Coming to the C++ Language

by Jonathan Hoyle

jonhoyle@mac.com

March 2007

Last month we explored some of the useful features of the Standard C and C++ Libraries. This month, we look forward to the changes ahead in the upcoming C++ Language and updates to the Standard Library.

The Evolution of C++09

After the first C++ specification was ratified in 1998, a deliberate 5 year period of silence was taken to allow compiler vendors to catch up with the standard, and for the committee to receive defect reports against the C++98 spec. At the end of this period, the ANSI Standards Committee released an updated ANSI/ISO specification containing bug fixes and wording improvements. These defects were documented in Technical Corrigenda 1 in 2003. These changes were mostly minor, reiterations of things intended but were not properly spelled out. The biggest change coming out of it was the making explicit that memory allocated by the std::vector<> container must be contiguous. This updated version of C++ is referred to as C++ + TG1, or sometimes simply C++03.

Subsequent to that, committee members began by accepting proposals on various changes to C++. This initiative was called C++0x, meaning that the expectation was to ratify a new version of the language sometime in 200x. As time has passed, it has become obvious that the new language cannot be ratified any earlier than 2009, and so recently the initiative has become named C++09.

A Technical Report on Library Extensions was initiated in 2004 and was completed in January 2005 (this report became known as TR1). It recommended a number of extensions to the C++ Standard Library, many of which came from the Boost framework. By April 2006, the Standards Committee accepted all the recommendations of TR1 with the exception of certain high-level mathematics libraries (which were thought to be too difficult for some vendors to implement). GCC 4.0 already has a port of much of TR1, all under the std::tr1:: namespace, so Xcode users may begin using them now. Metrowerks CodeWarrior 9 & 10 also has a port of TR1.

More details on the TR1 draft is available in the Open Standard document.

[Note: A second Technical Report on Library Extensions was initiated in April 2005 for further research, but this report is not expected to conclude before C++09 is finalized.]

The Standards Committee intends to complete the C++09 document by the end of 2007. There are two meetings planned in 2007 to finalize all outstanding issues: April 2007 in Oxford, UK and October 2007 in Kona, Hawaii. Assuming no other delays, a completed document for general review should be available in 2008, and so ratification can take place sometime in 2009.

Philosophy of C++09

With the ratification of C++98 being less than 10 years ago, the Standards Committee was not interested in any large, sweeping changes in the language. Instead, it was interested primarily in changes which make the language easier to use and more accessible to beginning programmers. One of the complaints that many have with C++ is that it is too expert-friendly. Many programmers are not interested in being experts at any programming language; rather, they wish to be experts in their fields and simply use C++. Although a few new powerful features are being added to the language, simplifying to a reasonable level is a primary goal.

Another goal is to err on the side of updating the Standard Library before changing the language core. Changing the language itself is inherently more risky and leads to greater compatibility problems. Library enhancements allow for greater flexibility with less risk. Consider automatic memory garbage collection for example: modifying the core language for self-cleanup (as Java and C# do) would involve much greater change in the language and necessarily require a lack of backward compatibility. However, supplying a smart pointer class into the Standard Library would give the user these same capabilities without loss of backward compatibility.

Finally, the committee strived for improving real-world performance whenever possible. One of C++'s greatest strengths is its performance relative to newer languages like C# and Java. The user base is aware of this, and many have made C++ their programming language of choice for this very reason. In 2003, IDC reported that there are over 3 million full-time C++ developers; it makes sense to improve the language for their usability and not try to turn it into something it's not.

The Cautionary Tale of EC++

In 1999, a consortium of Japanese embedded systems tool developers (including NEC, Hitachi, Fujitsu and Toshiba) submitted a proposal for a language subset of C++. This subset would essentially be C++ with a number of language features removed which (they thought) were too complicated and could hurt performance. The features targeted for removal included: multiple inheritance, templates, exceptions, RTTI, the new cast operators and namespaces. This new language subset would be called Embedded C++, or EC++ for short.

To the surprise of the consortium members, the EC++ compilers were not only no faster than their C++ counterparts, but in some domains EC++ was actually slower! C++ founder Bjarne Stroustrup later explained that templates were used in much of the Standard Library to improve performance, and their removal put EC++ at a disadvantage. Upon hearing this, the dismayed members of the EC++ consortium scrambled to put together a new proposal: Extended EC++, which was simply EC++ with templates put back in.

When Extended EC++ compilers became available, they were once again put to the test against their C++ cousins. To the consortium's bewilderment, once again the performance gains relative to C++ turned out to be negligible. Part of the problem was the consortium's ignorance of C++'s Zero Overhead Principle: "What you don't use, you don't pay for". After this final embarrassment, ISO refused endorse any of the EC++ proposals.

In 2004, inspired by the EC++ debacle, the C++0x committee called for a Performance TR to determine which features of the C++ language truly had the greatest penalties in performance. As it turned out, there were only three areas where there were any measurable performance issues:

            1) free store (new and delete)
            2) RTTI (dynamic_cast<> and typeid())
            3) exceptions (throw and catch)

Memory allocation and deallocation turned out to have the largest impact on performance; however, it is unlikely that you would want to use a language that did not allocate memory from the heap. As for RTTI and exception handling, many compilers have switches allowing one to disable these as desired. Modern compilers have greatly optimized their implementation of Exception Handling, making RTTI the only outlier. In any case, with the Zero Overhead Principle in place, simply not using a C++ language feature is no different than having it removed.

As for EC++, Stroustrup is quoted as saying, "To the best of my knowledge EC++ is dead, and if it isn't it ought to be."

To view the Performance TR, visit this link.

Embarrassments, Fixes & Improvements

Although the C++ standard in 1998 was an astounding achievement, there were a small number of flaws that remained. Some of these were simply oversights; others were known problems, but there had not been sufficient agreement by the committee for resolution. Bjarne Stroustrup described some of these as embarrassments, particularly when trying to explain them to novices. These improvements include:

            vector<vector<int>> x;                                  // Finally, legal!
            vector<double> x = { 1.2, 2.3, 3.4 };              // Initializing STL containers
            stronger typing of enum's                               // Enumerated types remain in their scope
            extern-ing of template's                                 // No duplication across translation units

If you are not familiar as to how any of the above caused errors, you needn't even bother to understand why. They are problems that are going away in C++09. I will delve into the first item only (Stroustrup's biggest embarrassment), to give you a flavor of the problem. The flaw lies simply with the fact that C++98 parses the ">>" portion of vector<vector<int>> x; as a right shift operator and generates an error; C++09 fixes this. One of the reasons this took so long to resolve was that ANSI/ISO committee members are very hesitant to put in silent changes in the specification. A silent change is one that would keep the meaning of some C++ code with generating an error. Surprisingly, the reinterpretation of ">>" within templates can yield a silent change, as this example shows:

         template<int I>

         struct X
         {
             static int const x = 2;
         }
 
         template< >
         struct X<0>
         {
             typedef int x;
         }
 
         template<typename T>
         struct Y
         {
             static int const x = 3;
         }
 
         static int const x = 4;
 
         cout << (Y<X<1>>::x>::x>::x) << endl;       // C++98 prints “3”
                        // C++09 prints “0”

ANSI/ISO C99 Synchronization

One year following the ratification of C++, the ANSI/ISO C specification was updated with a number of improvements for its language. Many of these improvements were simply acquiring behavior that was already legal in C++ but seemed to make sense for C as well. Other changes were not part of C++, but the ANSI/ISO C++ committee in turn saw some of these features as valuable and are rolling these into the C++09 specification. These include:

            __func__                    // returns the name of the function within which it resides
            long long                    // extended integral type, typically used for 64-bit integers
            int16_t, int32_t, intptr_t, etc.           // specific integer types as defined in <stdint.h>
            Hex floating point types, eg: double x = 0x1.F0;
            Complex versions of some math functions, such as arcsin(), arccos(), fabs(), etc.
            Variadic macros, that is macros taking a variable number of arguments, such as:
                        #define S(...) sum(__VA_ARGS__)

More information on C99's Synchronization with C++09 is available in the Open Standard's document.

Standard C++ Library Enhancements

The Standard C++ Library, including STL (the Standard Template Library), is a generous supply of useful containers and utilities. Despite its fullness of capabilities, there were still a number of components users felt were missing. C++09 fills these gaps with the following new library classes:

            regex: a long awaited regular expressions class
            array<>: a 1-dimensional array class containing its size (can be size 0)
            STL hash classes: unordered_set<>, unordered_map<>, etc.
                        (These do the same thing as their ordered counterparts, except using a hash table)
            tuple<>: a templated tuple class of multiple types: tuple<int,int> x; tuple<double,void *,A,B> z;

Mac users are fortunate in that they do not have to wait until 2009 for these Standard Library changes: they are available today in gcc 4 (the compiler inside Xcode 2.x). These additions are within the library namespace std::tr1:: ("tr1" stands for Technical Report #1, the standard's committee report defining these new classes).

For more information on Standard C++ Library Enhancements, read ISO's public draft.

Thread Enhancements

Mac OS X programmers who have had to write Unix level multithreaded code will heartily embrace the new thread-capable features of C++09. Below are some examples of these:

Thread Local Storage:

            thread int x = 1;       // global within the thread

Atomic Operations:

            atomic
            {
                . . .                            // pauses other threads during scope
            }

Parallel Execution:

            active
            {
                { . . . }                       // first parallel block
                { . . . }                       // second parallel block
                { . . . }                       // third parallel block
            }

In the case of Parallel Execution, it is likely that active will be implemented similarly to the register and inline keywords in that it will be a compiler request only. If the compiler find that the overhead of creating parallel blocks in some given instance outweighs the savings, it will be free to ignore the request and run the blocks serially.

Clearly, these language features greatly simply coding which would otherwise require the use of pthread's, mutexes and a number of other objects. It should be pointed out that the aforementioned features are still being debated by C++09 committee members, so there may be some changes to what I have described prior to ratification.

For more information on Thread Enhancements, read this paper.

Variadic Templates

For years, the C language has allowed functions to have a variable number of parameters. Unfortunately, this was not true of template arguments with C++98. In C++09, templates can have a variable number of types. Here is an example in which a templated DebugMessage() function can take advantage of variadic templates:

         // Prints to stderr only when DEBUG flag set
         template<typenameÖ TypeArgs>
         void DebugMessage(TypeArgs… args)
         {
            #ifdef DEBUG
                        ...         // Implement writing to stderr
            #else
                                    // Do nothing if the DEBUG switch is off
            #endif
         }
 
         // Later in code
         DebugMessage(ìThe value of n = ì, n);
         DebugMessage(ìx = ì, x, ì, y = ì, y, ìz = ì, z);
         DebugMessage(ìTRACE:î,
                                    ì time = ì, clock(),
                                    ì, filename = ì, __FILE__,
                                    ì, line number = ì, __LINE__,
                                    ì, inside function: ì, __func__);

For more information on Variadic Templates, read this document.

Delegating Constructors

Other languages, such as C#, allow one class constructor to invoke another. In C++98, this feature did not exist, thus requiring the class designer to create a separate initialization function if it wished to use common code across multiple constructors. In C++09, this becomes available, as the following example shows:

         class X
         {
             public:
                 X();                                      // default constructor
                 X(void *ptr);                      // takes a pointer
                 X(int value);                       // takes an int
         };
 
         X::X(): X(NULL)                    // calls X(void *)
         {
             ...                                             // other code
         }
 
         X::X(void *ptr): X(0)              // calls X(int)
         {
             ...                                             // other code
         }
 
         X::X(int value)                         // does not delegate
         {
             ...                                             // other code
         }

For more information on Delegating Constructors, consult this working document.

Null Pointers

In ANSI C, NULL is defined as (void *) 0. In C++, the use of NULL is deprecated. Why? Because unlike in C, it is illegal in C++ to assign a void pointer to any other type of pointer:

         void *vPtr = NULL;                // legal C, legal C++
         int *iPtr = NULL;                   // legal C, illegal C++
                                                            // Cannot assign a void * to int * in C++!
         int *iPtr = 0;                            // legal C++

However, the proliferation of NULL in C++ code remains so great that many compilers simply generate a warning, not an error, when such a pointer assignment mismatch takes place. Others redefine NULL in C++ as 0, avoiding the cast error. Despite these occasional compiler courtesies, it is still very confusing for beginning C++ programmers to use null pointers, especially in examples such as these:

         void foo(int);                             // Takes an int
         void foo(char *);                       // Takes a char pointer
 
         foo(0);                                        // Is this supposed to be a ptr or the number 0?
         foo(NULL);                              // No matching prototype

So as to simplify the meaning and use of empty pointers, C++09 introduces nullptr, a type-safe null pointer which can be used with any pointer type, but is not compatible with any integral type:

         char *cPtr1 = nullptr;            // a null C++ pointer
         char *cPtr2 = 0;                      // legal, but deprecated
         int n = nullptr;                      // illegal
         X *xPtr = nullptr;               // can be used with any ptr type
 
         void foo(int);                             // Takes an int
         void foo(char *);                       // Takes a char *
         foo(0);                                        // Calls foo(int)
         foo(nullptr);                              // Calls foo(char *)

More information on nullptr, can be viewed here.

The Amazing Return of auto

When the C language first evolved, the auto keyword was used to specify to the compiler that a variable was being allocated on the stack, for example:

         auto x;                     /* a variable named x, implicitly an int, is placed on the stack */

When ANSI C was ratified in 1989, the implicit int rule was removed:

         auto x;                     /* illegal in ANSI C */
         int x;                       /* OK, auto assumed */
         auto int x;                   /* OK, but redundant */

Since that time, auto remained a keyword in the C (and later C++) languages, even though virtually no one had used it since the 1970's. After over 30 years of disuse, the C++09 standard introduces the auto keyword to mean the variable type is implied by the initializer:

         auto x = 10;             // x is an int
         auto y = 10.0;          // y is a double
         auto z = 10LL;        // z is a long long
         const auto *p = &y;     // p is a const double *

The savings becomes more significant with complicated types, such as the following example:

         void *foo(const int doubleArray[64][16]);
         auto myFcnPtr = foo;              // myFcnPtr is of type "void *(const int(*)[16])"

In addition, auto becomes useful for temporary variables whose types aren't important but merely just have to match. Consider the following function which walks through an STL container:

         void foo(vector<MySpace::MyClass *> x)
         {
             for (auto ptr = x.begin(); ptr != x.end(); ptr++)
             {
                 ... // Code modifying the data
             }
         }

Without auto, the type for variable ptr would be vector<MySpace::MyClass *>::iterator. Moreover, any change to this container, such as changing it from a vector<> to a list<>, or changing the class name or namespace, would require changes in the ptr variable definition, despite the fact its type is completely unnecessary for the for-loop (other than for the compiler).

As an interesting aside, a future version of the C# language is expected to implement a similar behavior using the var keyword.

Note that an initializer is still required to use auto for C++09:

         auto x; // still illegal in C++09

But suppose you knew what type you wanted (based upon another variable) but did not want to initialize? The new decltype keyword is available for just such purposes, as the following example shows:

         bool SelectionSort(double data[256], double tolerance);
         bool BubbleSort(double data[256], double tolerance);
         bool QuikSort(double data[256], double tolerance);
 
         decltype(SelectionSort) mySortFcn;
 
         if (bUseSelectionSort)
               mySortFcn = SelectionSort;
       else if (bUseBubbleSort)
            mySortFcn = BubbleSort;
        else
            mySortFcn = QuikSort;

For more information on auto & decltype, check out this link.

Smart Pointers

Smart Pointers are objects pointing to memory which are smart enough to know when to delete themselves, rather than rely upon the user to manage its deallocation. Virtually all modern languages, such as Java and C#, manage memory in this fashion and thus avoiding memory leakages and overstepping. The C++98 Standard Library came a minimally smart pointer object, auto_ptr<>. Unfortunately, auto_ptr<> had some severe limitations to it, the most notable of which is that it used an exclusive ownership model. That is, the last auto_ptr<> receiving the assignment was the sole owner of the memory:

         auto_ptr<int> ptr1(new int[1024]);              // ptr1 has exclusive access
         auto_ptr<int> ptr2 = ptr1;                            // ptr2 has exclusive access, ptr1 no longer

This is counter-intuitive, as one does not expect the source object to change in such an assignment. The C++ community has by and large rejected auto_ptr<> and its use is now rather minimal.

C++09 Standard Library introduced a smarter pointer object, shared_ptr<>. Its main difference over auto_ptr<> is that it uses a shared ownership model using reference counting to determine when the memory should be deallocated. For example:

         main()
         {
             shared_ptr<int> ptr1;                                              // null smart ptr
             ...
             {
                 shared_ptr<int> ptr2(new int[1024]);
                 ptr1 = ptr2;                                                             // both ptr1 & ptr2 own it
             }
             // ptr2 destructed, only ptr1 owns it
             // memory not yet deallocated
         }
         // ptr1 destructed, now delete is called on it

A shared_ptr<> can be treated as a pointer, so it can be dereferenced like *ptr1 or call call methods upon the underlying data such as ptr1->foo(). The following are some constructors for shared_ptr<> that make it useful to use:

explicit shared_ptr<T>(T *ptr);                  // Attaching to memory
shared_ptr<T>(T *ptr, Fcn delFcn);          // Attaching to memory and user-defined delete fcn
shared_ptr<T>(shared_ptr<T> ptr);         // Copy constructor
shared_ptr<T>(auto_ptr<T> ptr);             // Converting from an auto_ptr<>

Note this last constructor converts the data from an auto_ptr<> to a shared_ptr<>, making it easier for you to transition your previous code. There are some additional utilities made available as well, such as a swap() routine and two cast routines: static_pointer_cast() and dynamic_pointer_cast().

Fortunately for Mac programmers, shared_ptr<> is part of the std::tr1:: namespace, and thus is already available to Mac users using Xcode 2.x or higher.

For more information on shared_ptr, visit InformIt's web page on the topic.

Rvalue References

In C, function parameters are always passed by value; that is, a copy of the parameter always is passed, never the actual parameter. To modify a variable in a C, the function must pass the parameter's pointer, and then dereference the pointer internally to modify its data:

         void foo(int valueParameter, int *pointerParameter)
         {
             ++valueParameter;   // parameter passed by value, modifications are local to this copy
             ++pointerParameter; // pointer passed by value, modifications are local to this copy
             ++*pointerParameter; // dereferencing pointer, modifications are permament
         }

One of the powerful new features that C++ introduced over C was that of a reference, using the & operator. Functions in C++ could then have parameters passed by reference, thus allowing its data to be modified directly without the need of a pointer:

         void foo(int valueParameter, int &referenceParameter)
         {
             ++valueParameter;               // passed by value, modifications are local to this copy
             ++referenceParameter;         // passed by reference, modifications are permament
         }

References must be to lvalues, that is variables which can be modified. Rvalues, read-only or temporary memory, cannot be used:

         int          myIntA = 10;
         int          myIntB = 20;
 
         foo(myIntA, myIntB);             // myIntA stays at 10, myIntB becomes 21
         foo(1, myIntA);                              // 1 passed in by value, myIntA becomes 11
         foo(myIntA, 1);                              // Error: 1 is an rvalue and cannot be passed in
         foo(0, myIntB + 1);                  // Error: myIntB+1 is an rvalue and cannot be passed in

Occasionally, it is useful to pass a parameter by reference even when there is no desire to modify its contents. This is particularly true when a large class or struct is being passed to the function, and you wish to avoid creating a copy of the large object:

        void foo(BigClass valueParameter, const BigClass &constRefParameter)
         {
             ++valueParameter;               // passed by value, so modifications are tempoarary
             ++constRefParameter;         // compiler error, cannot modify a const parameter
         }

In C++09, a new type of reference is defined, that of an rvalue reference (the familiar type of reference from C++98 is now referred to as an lvalue reference). Rvalue references can bind to temporary data but act on it directly without the need of a copy. The && operator indicates that a reference is an rvalue reference:

        void foo(int valueParameter, int &lvalRefParameter, int &&rvalRefParameter)
         {
             ++valueParameter;               // parameter passed by value, so modifications are local
             ++lvalRefParameter;            // lvalue reference makes changes permanent
             ++rvalRefParameter;            // rvalue reference makes changes local without a copy
         }
 
         foo(0, myIntA, myIntB + 1);  // The temporary value myIntB + 1 is not copied but moved

One of the chief benefits of rvalue references is the ability to take advantage Move Semantics, that is, moving data from variable to variable without copying. A class can define a Move Constructor instead of, or in addition to, a Copy Constructor as so:

        // Class definition
        class X
         {
             public:
                 X();               // Default Constructor
                 X(const X &x);       // Copy Constructor (lvalue ref)
                 X(X &&x);       // Move Constructor (rvalue ref)
         };
 
        // Utility function returning X
         X bar();
         X x1;                            // Default construction of x1
         X x2(x1);                     // x2 created as a copy of x1
         X x3(bar());                // bar() returns a temporary X, memory moved directly into x3

The primary motivation behind Move Semantics is improving performance. As an example, let us suppose you have two vectors of strings which you would like to swap data between. Using standard Copy Semantics, an implementation might look like this:

        void SwapData(vector<string> &v1, vector<string> &v2)
         {
             vector<string> temp = v1; // A new copy of v1
             v1 = v2;     // A new copy of v2
             v2 = temp;      // A new copy of temp
         };

Using Move Semantics, you can bypass all of that copying:

        void SwapData(vector<string> &v1, vector<string> &v2)
         {
             vector<string> temp = (vector<string> &&) v1;   // temp now points to same data as v1
             v1 = (vector<string> &&) v2;                      // v1 now points to same data as v2
             v2 = (vector<string> &&) temp;                 // v2 now points to same data as temp
         };
         // No copies are made, only pointers are exchanged!

For more information on Rvalue References, view the proposal document online.

Concepts

Concepts are constraints on types, useful for templated classes and functions. Take for example the definition of std::min<>():

template<typename T>
const T &min(const T &x, const T &y)
{ return (x < y) ? x : y; }

This definition for min<>() allows for any type to be passed into it, despite the fact that it makes sense only when the templated type has the < operator defined upon it. Modifying this definition so that it takes a concept instead of a generic type solves this problem, and thus allows min<>() to be defined differently on another set of types that does not define the < operator. First, we define the concept:

auto concept LessThanComparable<typename T>
{
    bool operator<(T, T);   // We require the < operator be defined
};

With our concept now defined, we can modify the definition of std::min<>()to use a concept instead of a type, as follows:

template<LessThanComparable T>
const T &min(const T &x, const T &y)
{ return (x < y) ? x : y; }

For more information on Concepts, you can read the the Concepts document here.

Other Additions in C++09

In addition to the features already described, here is a small list of some other sundry additions being added to the C++09 language:

New char types: chart16_t, char32_t
Static asserts (from Boost::)
Template Aliasing
Overloading operator .()
Type Traits: is_pointer(), is_same()
New for loop (a la foreach)
Extern Templates
New Random Number Generator

Advanced/Active Topics

There are a number of other features that the C++ Standards committee is still debating. Although it is likely that at least one or two of these will make it into the final standard, time is simply not permitting to allow all of them in. Here is a partial list of some topics which may, or may not, become part of the C++09 standard:

Transparent Garbage Collection
Dynamic Library Support
Memory Alignment Facilities
Explicit Conversion Operators
Extended Friend Declarations
Explicit Namespaces
Extensible Literals

Not Available for C++09

There are many very cool features that were discussed by the ISO committee that just simply lack sufficient consensus or priority to be available for C++09. For example, one of the most common requests was the creation of a standard cross-platform GUI API, but such a thing was way out of scope for anything the standards committee would formalize. Others, such as Modules, seemed very much in reach but was not as important as other features. Here is a partial list of items that (unless something miraculous happens) will not be available in C++09:

Infinite Precision Arithmetic
Properties & Events
Contract Programming
Exclusive Inheritance
Decimal Library
A Full Multithreaded API
Modules

The C++0x Standard Library Wish List is (as of this writing) up to revision 6 and can be read online.

Further Reading

The actual proposals for C++09 are available to read from the C++ Standards Committee web site. In addition, the following are links to various articles discussing C++09:

C++09: The Road Ahead
C++09: Proposals by Statuses
C++: Predictions for 2007
The State of C++ Evolution
Toward a Standard C++0x Library
A Brief Look at C++0x
C++0x: The New Face of Standard C++
The Design of C++0x
C++0x: Wikipedia Entry
C++ in 2005

Conclusion

Many of the changes in the next version of C++ are available to programmers today, particularly those that are a part of the Standard Library. Even of those not yet available, C++ developers ought to prepare themselves for the new language features. With increased readability and comprehension, C++ appears to have a very exciting future, even still.

Next month: Moving CodeWarrior Projects to Xcode.

http://www.maccompanion.com/archives/March2007/Columns/AccordingtoHoyle.htm http://www.maccompanion.com/macc/archives/March2007/Columns/AccordingtoHoyle.htm