Gnarly New C++ Language Features

Gnarly New C++ Language Features
(that you can finally use)

by Nathan C. Myers
<http://www.cantrip.org/>

Abstract:

Standard C++ is not the C++ we know. Its many new features enable you to write programs and libraries that cannot be written in any other language. This has been academic until now, because compilers that support the new features have not been available, but that has changed. In this talk I explore the more interesting of these new features and some examples of what can be done with them. [Ed. note: Gnarly is American slang; it suggests engaging and powerful.]


Overview

  • History
  • Key Features
  • Examples
  • Conclusion

  • History

  • Standard C++ vs. Traditional C++
  • Making C++ More Useful
  • Availability

  • Standard C++ vs. Traditional C++

  • C++ released in 1985: "cfront"; features included:
  • "better C"
  • classes, virtual, overloading
  • Later versions added features:
  • multiple inheritance
  • templates
  • Annotated Reference Manual "ARM" in 1989 added:
  • exceptions
  • ISO/ANSI Committee convened end of 1989
  • adopted ARM, iostream library
  • main job was to clarify language definition
  • secondary job was to make the language more useful
  • Committee voted out "Committee Draft 2" (CD2) in November 1996; language is officially stable.

  • Making C++ More Useful

  • Job of a language standard committee is whatever people interested enough to join choose.

  • Ratifying existing practice is safest. Is it best?

  • All kinds of language users were represented on the committee:
  • compiler implementors
  • library designers
  • test-suite writers
  • teachers
  • programmers
  • Commercial interests favor a simpler language.

  • All language extension proposals faced fierce opposition, and most were rejected.

  • Availability

  • Some of the most powerful features were a long time being implemented.

  • Several compilers now implement all the features discussed below.

  • Compilers based on EDG's front end, and HP's, are most nearly complete, with Egcs coming up fast.
  • Silicon Graphics, Apogee, Kuck & Associates, many others use it;
  • All such compilers are on UNIX systems, thus far, except Intel VTune.
  • Cheapest EDG-based compiler is from Greg Comeau, www.comeaucomputing.com;

  • Cost is only US$50
  • Ported to various UNIX, Linux; MS available soon.
  • Others on Linux include Kuck & Associates and Portland Group. Egcs (a non-EDG compiler) now supports all the discussed features.


  • Gnarly New Language Features

    Most changes are useful but not fun:
    e.g. "bool", "static_cast<>", "explicit" constructors (Namespaces actually are fun, but need a paper of their own.)

    Some let us do things we couldn't do before. This table lists the fun ones, and which of them are supported in the more advanced compilers.

    Gnarly New Feature:   EDG Sun HP Egcs
    Member Templates   X . X X
    Partial Specialization of Class Templates   X . X X
    Overloading of Function Templates   X X X X
    Template Default Arguments   X . X X
    Explicit Template Function Qualification   X X X X
    (Last updated June 1998)

    These can be used to do things that can't be done in any other compiled language.

    (Note: Compilers based on EDG's front end aren't necessarily based on the latest version.)

    The next few slides describe each language feature separately, before the gnarly examples.


    Member Templates

    Regular templates are global:

      template <class T> void swap(T& a, T& b)
        { T tmp = a; a = b; b = tmp; }
    

    Member templates are declared in a class definition:

      template <class T, ...>
        class list {
          .
          .
          typedef  .  .  . iterator;  // the regular list<> iterator
          .
          .
          template <class InputIter>
            void insert(iterator position,
    	            InputIter first, InputIter last);
        };
    

    This inserts the sequence of values from first to last for any iterator type InputIter.

    Member operators & constructors can be templates.


    Partial Specialization

    Regular specialization binds all arguments:

      template <class T, Class U>
        class hashtable { . . . };
    
      template <>
        class hashtable<int,int> { . . . };
      template <>
        class hashtable<int,float> { . . . };
        . 
          .
            .
    

    Partial specialization leaves one or more arguments unbound:

      template <class U>
        class hashtable<int, U> { . . .  };
    


    Function Template Overloading

    Regular function templates can be specialized by binding all parameters:

      template <class Base, class Exponent>
        Base pow(Base b, Exponent e);
    
      template <>
        int pow(int b, int e);
    

    Function template overloading can leave an argument unbound:

      template <class Base>
        Base pow(Base b, int);
    

    Notice that function template overloading isn't the same as partial specialization; the second template declaration above is a separate function template, and the compiler will choose which to use for each call. Besides, the overloaded template might have more parameters than what you might consider the "base" template:

      template <class T>
        void f(T);
    
      template <class U, class V, class W>
        void f(V (U::*)(const W&));
    


    Template Default Argument

    Regular template arguments must all be supplied, or deduced from function arguments:

      template <class T, class Compare>
        class set;
    
      set<int,less<int> > ints;
    

    Default arguments allow users to leave off parameter values in the common case:

      template <class T, class Compare = less<T> >
        class set;
    
      set<int> ints;  // same as "set<int,less<int> > ints;"
    

    Notice that the default binding (in this case, less<T>) can depend on previous arguments (in this case, T). (!)


    Explicit Template Function Qualification

    Regular template function parameters must be deduced from a function argument declaration:

      template <class T, class U>
        U implicit_cast(const T& t, const U*) { return t; }
    
      // string str = "hello";  // invisible conversion
      string str = implicit_cast("hello", (string*)0);  // visible conversion
    

    (implicit_cast<> performs the cast only if it would have been legal to leave it out; otherwise it's an error. It's safer than a "C"-style cast, but makes it clear to the reader what's going on. However, the syntax above is ugly; in traditional C++ you can't do better without a macro.)

    Explicit Qualification permits direct specification, bypassing argument type deduction, and with a nice syntax:

      template <class U, class T>
        U implicit_cast(const T& t) { return t; }
    
      string str = implicit_cast<string>("hello");
    

    implicit_cast is not part of the core language, because it is easily implemented in library code. It does not appear in the Standard C++ Library mainly because it was proposed too late, after the committee had resolved not to add anything more.


    Cool Examples

    The point of these features is that they let us write more powerful libraries that are easier for users, because they absorb work or complexity users would have to manage themselves otherwise. Of course we're all users . . .

    The next few sections explore examples of how this power can be applied.

  • Optimization
  • In-line vector operations
  • Traits
  • Conversions
  • Compile-time computation

  • Optimization

      template <class T>
        void swap(T& a, T& b)
          { T tmp = a; a = b; b = tmp; }  // copy b once, a twice
    

    This could be very expensive, for some T:

      vector<string> a, b;
      ...
      swap(a,b);  // copy whole vector three times. (!)
    

    But we can declare a special case for vector<anything>:

      template <class T>
        void swap<vector<T> >(vector<T>& a, vector<T>& b)
          { a.swap(b); }  // just change pointers
    

    This swap is fast for all vectors. The Standard C++ Library includes partial specializations like this for all the containers.


    In-line Vector Operations

    With the following in a header file...

    a forward declaration of the recursive template:

      template <int Dim, class T>
        struct dot_class;
    
    a specialized base case for recursion:
      template <class T>
        struct dot_class<1,T> {
          static inline T dot(const T* a, const T* b)
            { return *a * *b; }
        };
    
    the recursive template:
      template <int Dim, class T>
        struct dot_class {
          static inline T dot(const T* a, const T* b)
            { return dot_class<Dim-1,T>::dot(a+1,b+1) + *a * *b; }
        };
    
    ... and some syntactic sugar:
      template <int Dim, class T>
        inline T dot_product(const T* a, const T* b)
          { return dot_class<Dim,T>::dot(a, b); }
    

    Then

      int product = dot_product<3>(a, b);
    

    results in the same (optimal) code as

      int product = a[0]*b[0] + a[1]*b[1] + a[2]*b[2];
    

    by way of the compile-time template expansions

      int product = dot_class<3,int>::dot(a,b);
      int product = dot_class<2,int>::dot(a+1,b+1) + *a * *b;
      int product = dot_class<1,int>::dot(a+2,b+2) + *(a+1) * *(b+1) + *a * *b;
      int product = *(a+2) * *(b+2) + *(a+1) * *(b+1) + *a * *b;
    

    This technique has been demonstrated for

  • vectors,
  • FFT (signal processing, image processing),
  • polygon transformations (4x4 matrix multiply).
  • and results in code as good as specially optimized FORTRAN, but from an ordinary C++ compiler.

    For more (and even more impressive) examples, see http://oonumerics.org/blitz/, work by Todd Veldhuizen.


    Template Typedef

    C++ has no template typedef. However, a member template can be used to fake it. In this example, it is called rebind<>:

      template <class T>
        class allocator {
          . . .
    
          template <class U>
            struct rebind { typedef allocator<U> other; };
          . . .
    
          template <class U>
            allocator(const allocator<U>&);
        };
    

    Here is an authentic use of it, taken from the Standard Library.

      template <class T, class Allocator = allocator<T> >
        class list {
         private:
          typedef  . . .  listnode;
          typedef typename Allocator::rebind<listnode>::other
                  Node_allocator;
    
          Node_allocator alloc_;
    
          list_node* make_node() 
            { return new(alloc_.allocate(1)) list_node; }
    
         public:
          list(const Allocator& a = Allocator())
            : alloc_(a) { }  // implicit conversion
          . . .
     
        };
    

    A list<> doesn't need to allocate objects of type T; it needs to allocate listnodes. The member rebind<> lets list<> name the allocator type that it needs, given only the template argument Allocator.

    Note the new keyword typename above. It's just a syntax hack so that the C++ lexer can tell that a type name follows.


    Traits

    Traits template arguments allow libraries to extend flexibility from compile time to run time without harming convenience.

    Default template arguments add flexibility.

      template <
          class T,
          class Allocator = allocator<T> > // default at compile time
        class list {
          explicit list(
            const Allocator& = Allocator());  // default at run time
        };
      
      list<int> li;  
      // same as: list<int,allocator<int> > li(allocator<int>());
    

    Or

      template <class Char, class Traits = char_traits<char> >
        class streambuf;
    

    See http://www.cantrip.org/traits.html

    Traits allow return types to be deduced for function templates. Without them, this is what the STL iterator-difference function must look like:

      template <class Iterator, class Size>
        void distance(Iterator first, Iterator second, Size& total);
    
      distance(p, p+10, dist);  // ick: stores difference into dist
    
    because template parameters can't be deduced from return types.

    The Standard Library now has iterator traits. Most Iterators are expected to have a member typedef distance_type:

      template <class Iterator>
        struct iterator_traits {
          typedef typename Iterator::distance_type distance_type;
          . . .
        };
    
    Pointers have no distance_type member; but ptrdiff_t is right for any pointer, so specialize:
      template <class T>
        struct iterator_traits<T*> {
          typedef ptrdiff_t distance_type;
            .  .  .
        };
    
    Now a distance() can be declared that can be used like any other function:
      template <class Iterator>
        typename iterator_traits<Iterator>::distance_type
        distance(Iterator first, Iterator last);
    
    A use of it looks like this:
      dist = distance(p, p+10);  // better.
    

    Conversions

    This is the definition of pair as it appears in the Draft Standard C++ library:
      template <class A, class B>
        struct pair {
          typedef A first_type;
          typedef B second_type;
    
          A first;
          B second;
    
          pair()                       : first(A()), second(B()) { }
          pair(const A& a, const B& b) : first(a),   second(b)   { }
    
          template <class U, class V>
            pair(const pair<U,V>& p)   
    	  : first(p.first), second(p.second) { }
        };
    

    With the template constructor, the type

      pair<int,int>
    

    converts to

      pair<const int,int> 
    

    for use with map<>::insert(). (The lack of this conversion is probably the single most common complaint about the public domain STL.)

    Note: default definitions of copy constructor and assignment operator are still generated -- the template copy constructor does not prevent it. For pair that's OK, but some classes need to declare both the template copy constructor and a regular copy constructor. Caveat Scriptor!

    Explicit Qualification allows any named conversion.

    From Standard Library, Chapter 22, the class locale encapsulates formatting and other cultural preferences.

      class locale;
    
    A global function template extracts those preferences.
      template <class Facet>
        const Facet& use_facet(const locale&);
    
    For example, you could design a time zone preference
      class TimeZone : public std::locale::facet {
        . . .
        static std::locale::id id;     // used by use_facet<>()
        int minutes_off() const; 
      };
    
    and stow it in a locale object; then extract it using a notation similar to a cast:
      locale here;
        . . .
      int offset = use_facet<TimeZone>(here).minutes_off();
    

    Compile-time Computation

    The C++ template instantiator is a primitive-recursive function evaluator. This was discovered by Erwin Unruh, who walked into a standards meeting one day with a listing of an illegal program that made the compiler emit a list of prime numbers in its error messages.

    The same tricks are usable in working programs. Given N, compute a compile-time table size ceil(sqrt(N)):

      template <int Size, int Low = 1, int High = Size> struct Root; 
    
      template <int Size, int Mid>
        struct Root<Size,Mid,Mid> { static const int root = Mid; };
    
      template <int Size, int Low, int High>
        struct Root { 
          static const int mean = (Low + High)/2;
          static const bool down = (mean * mean >= Size);
          static const int root = Root<
            Size, (down ? Low : mean+1), (down ? mean : High)>::root;
        };
    
      // compute sqrt, use it for static table size
      int table[Root<N>::root];
    

    (This example also draws on work by Todd Veldhuizen.)


    Conclusion

  • Standard C++ is much more powerful than traditional C++.
  • Compilers that support new standard features have been a long time coming.
  • The new features have fun uses.

  • Here are some definitions:

  • function template: A template that describes how to create a set of functions.
  • template function: A function generated (or specialized) from such a template.
  • class template: A template that describes how to create a set of classes.
  • template class: A class generated (or specialized) from such a template.

  • Return to top. Send email: ncm-nospam@cantrip.org
    Copyright ©1997 by Nathan Myers. All Rights Reserved. URL: <http://www.cantrip.org/gnarly.html>