The Wayback Machine - https://web.archive.org/web/20090815083852/http://www.codeguru.com:80/cpp/cpp/algorithms/math/article.php/c9871/

CodeGuru
Earthweb Search
Login Forums Wireless Jars Gamelan Developer.com
CodeGuru Navigation
RSS Feeds

RSSAll

RSSVC++/C++

RSS.NET/C#

RSSVB

See more EarthWeb Network feeds

follow us on Twitter

Member Sign In
User ID:
Password:
Remember Me:
Forgot Password?
Not a member?
Click here for more information and to register.

jobs.internet.com

internet.commerce
Partners & Affiliates
















Home >> Visual C++ / C++ >> C++ >> Algorithms & Formulas >> Mathematics


Use Traits Classes for Information About Types
Rating:

Scott Meyers (view profile)
May 26, 2005

Go to page: 1  2  Next


(continued)




The following is an excerpt from Scott Meyers' new book, Effective C++, Third Edition: 55 Specific Ways to Improve Your Programs and Designs.

          

Item 47: Use traits classes for information about types.

The STL is primarily made up of templates for containers, iterators, and algorithms, but it also has a few utility templates. One of these is called advance. advance moves a specified iterator a specified distance:

template<typename IterT, typename DistT>    // move iter d units
void advance(IterT& iter, DistT d);         // forward; if d < 0,
// move iter backward

Conceptually, advance just does iter += d, but advance can't be implemented that way, because only random access iterators support the += operation. Less powerful iterator types have to implement advance by iteratively applying ++ or -- d times.

Um, you don't remember your STL iterator categories? No problem, we'll do a mini-review. There are five categories of iterators, corresponding to the operations they support. Input iterators can move only forward, can move only one step at a time, can only read what they point to, and can read what they're pointing to only once. They're modeled on the read pointer into an input file; the C++ library's istream_iterators are representative of this category. Output iterators are analogous, but for output: they move only forward, move only one step at a time, can only write what they point to, and can write it only once. They're modeled on the write pointer into an output file; ostream_iterators epitomize this category. These are the two least powerful iterator categories. Because input and output iterators can move only forward and can read or write what they point to at most once, they are suitable only for one-pass algorithms.

A more powerful iterator category consists of forward iterators. Such iterators can do everything input and output iterators can do, plus they can read or write what they point to more than once. This makes them viable for multi-pass algorithms. The STL offers no singly linked list, but some libraries offer one (usually called slist), and iterators into such containers are forward iterators. Iterators into TR1's hashed containers (see Item 54) may also be in the forward category.

Bidirectional iterators add to forward iterators the ability to move backward as well as forward. Iterators for the STL's list are in this category, as are iterators for set, multiset, map, and multimap.

The most powerful iterator category is that of random access iterators. These kinds of iterators add to bidirectional iterators the ability to perform "iterator arithmetic," i.e., to jump forward or backward an arbitrary distance in constant time. Such arithmetic is analogous to pointer arithmetic, which is not surprising, because random access iterators are modeled on built-in pointers, and built-in pointers can act as random access iterators. Iterators for vector, deque, and string are random access iterators.

For each of the five iterator categories, C++ has a "tag struct" in the standard library that serves to identify it:

struct input_iterator_tag {};
struct output_iterator_tag {};
struct forward_iterator_tag: public input_iterator_tag { };
struct bidirectional_iterator_tag: public forward_iterator_tag { };
struct random_access_iterator_tag: public bidirectional_iterator_tag {};

The inheritance relationships among these structs are valid is-a relationships (see Item 32): it's true that all forward iterators are also input iterators, etc. We'll see the utility of this inheritance shortly.

But back to advance. Given the different iterator capabilities, one way to implement advance would be to use the lowest-common-denominator strategy of a loop that iteratively increments or decrements the iterator. However, that approach would take linear time. Random access iterators support constant-time iterator arithmetic, and we'd like to take advantage of that ability when it's present.

What we really want to do is implement advance essentially like this:

template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d)
{
   if (iter is a random access iterator) {
      iter += d;                          // use iterator arithmetic
   }                                      // for random access iters
   else {
      if (d >= 0) { while (d--) ++iter; } // use iterative calls to
   else { while (d++) - -iter; }          // ++ or - - for other
   }                                      // iterator categories
}

This requires being able to determine whether iter is a random access iterator, which in turn requires knowing whether its type, IterT, is a random access iterator type. In other words, we need to get some information about a type. That's what traits let you do: they allow you to get information about a type during compilation.

Traits aren't a keyword or a predefined construct in C++; they're a technique and a convention followed by C++ programmers. One of the demands made on the technique is that it has to work as well for built-in types as it does for user-defined types. For example, if advance is called with a pointer (like a const char *) and an int, advance has to work, but that means that the traits technique must apply to built-in types like pointers.

The fact that traits must work with built-in types means that things like nesting information inside types won't do, because there's no way to nest information inside pointers. The traits information for a type, then, must be external to the type. The standard technique is to put it into a template and one or more specializations of that template. For iterators, the template in the standard library is named iterator_traits:

template<typename IterT>          // template for information about
struct iterator_traits;           // iterator types

As you can see, iterator_traits is a struct. By convention, traits are always implemented as structs. Another convention is that the structs used to implement traits are known as—I am not making this up—traits classes.

The way iterator_traits works is that for each type IterT, a typedef named iterator_category is declared in the struct iterator_traits<IterT>. This typedef identifies the iterator category of IterT.

iterator_traits implements this in two parts. First, it imposes the requirement that any user-defined iterator type must contain a nested typedef named iterator_category that identifies the appropriate tag struct. deque's iterators are random access, for example, so a class for deque iterators would look something like this:

template < ... >                          // template params elided
class deque {
public:
   class iterator {
   public:
      typedef random_access_iterator_tag iterator_category;
      ...
   }:
   ...
};

About the Author
Scott Meyers is one of the world's foremost experts on C++ software development. He wrote the best-selling Effective C++ series (Effective C++, More Effective C++, and Effective STL), wrote and designed the innovative Effective C++ CD, is consulting editor for Addison Wesley's Effective Software Development Series, and is a member of the advisory board for Software Development magazine. He also sits on technical advisory boards for several start-up companies. A programmer since 1972, he holds an M.S. in Computer Science from Stanford University and a Ph.D. from Brown University.

Go to page: 1  2  Next

Tools:
Add www.codeguru.com to your favorites
Add www.codeguru.com to your browser search box
IE 7 | Firefox 2.0 | Firefox 1.5.x
Receive news via our XML/RSS feed







RATE THIS ARTICLE:   Excellent  Very Good  Average  Below Average  Poor  

(You must be signed in to rank an article. Not a member? Click here to register)

Latest Comments:
Looks a lot nicer than an enum. but certainly not as efficient ! - riskservers (06/28/2005)
Excellent - Darka (06/03/2005)

View All Comments
Add a Comment:
Title:
Comment:
Pre-Formatted: Check this if you want the text to display with the formatting as typed (good for source code)



(You must be signed in to comment on an article. Not a member? Click here to register)