Follow Techotopia on Twitter

On-line Guides
All Guides
eBook Store
iOS / Android
Linux for Beginners
Office Productivity
Linux Installation
Linux Security
Linux Utilities
Linux Virtualization
Linux Kernel
System/Network Admin
Programming
Scripting Languages
Development Tools
Web Development
GUI Toolkits/Desktop
Databases
Mail Systems
openSolaris
Eclipse Documentation
Techotopia.com
Virtuatopia.com
Answertopia.com

How To Guides
Virtualization
General System Admin
Linux Security
Linux Filesystems
Web Servers
Graphics & Desktop
PC Hardware
Windows
Problem Solutions
Privacy Policy

  




 

 

Thinking in C++ Vol 2 - Practical Programming
Prev Home Next

Traits

The traits template technique, pioneered by Nathan Myers, is a means of bundling type-dependent declarations together. In essence, using traits you can mix and match certain types and values with contexts that use them in a flexible manner, while keeping your code readable and maintainable.

The simplest example of a traits template is the numeric_limits class template defined in <limits>. The primary template is defined as follows:

template<class T> class numeric_limits {
public:
static const bool is_specialized = false;
static T min() throw();
static T max() throw();
static const int digits = 0;
static const int digits10 = 0;
static const bool is_signed = false;
static const bool is_integer = false;
static const bool is_exact = false;
static const int radix = 0;
static T epsilon() throw();
static T round_error() throw();
static const int min_exponent = 0;
static const int min_exponent10 = 0;
static const int max_exponent = 0;
static const int max_exponent10 = 0;
static const bool has_infinity = false;
static const bool has_quiet_NaN = false;
static const bool has_signaling_NaN = false;
static const float_denorm_style has_denorm =
denorm_absent;
static const bool has_denorm_loss = false;
static T infinity() throw();
static T quiet_NaN() throw();
static T signaling_NaN() throw();
static T denorm_min() throw();
static const bool is_iec559 = false;
static const bool is_bounded = false;
static const bool is_modulo = false;
static const bool traps = false;
static const bool tinyness_before = false;
static const float_round_style round_style =
round_toward_zero;
};
 

The <limits> header defines specializations for all fundamental, numeric types (when the member is_specialized is set to true). To obtain the base for the double version of your floating-point number system, for example, you can use the expression numeric_limits<double>::radix. To find the smallest integer value available, you can use numeric_limits<int>::min( ). Not all members of numeric_limits apply to all fundamental types. (For example, epsilon( ) is only meaningful for floating-point types.)

The values that will always be integral are static data members of numeric_limits. Those that may not be integral, such as the minimum value for float, are implemented as static inline member functions. This is because C++ allows only integral static data member constants to be initialized inside a class definition.

In Chapter 3 you saw how traits are used to control the character-processing functionality used by the string classes. The classes std::string and std::wstring are specializations of the std::basic_string template, which is defined as follows:

template<class charT,
class traits = char_traits<charT>,
class allocator = allocator<charT> >
class basic_string;
 

The template parameter charT represents the underlying character type, which is usually either char or wchar_t. The primary char_traits template is typically empty, and specializations for char and wchar_t are provided by the standard library. Here is the specification of the specialization char_traits<char> according to the C++ Standard:

template<> struct char_traits<char> {
typedef char char_type;
typedef int int_type;
typedef streamoff off_type;
typedef streampos pos_type;
typedef mbstate_t state_type;
static void assign(char_type& c1, const char_type& c2);
static bool eq(const char_type& c1, const char_type& c2);
static bool lt(const char_type& c1, const char_type& c2);
static int compare(const char_type* s1,
const char_type* s2, size_t n);
static size_t length(const char_type* s);
static const char_type* find(const char_type* s,
size_t n,
const char_type& a);
static char_type* move(char_type* s1,
const char_type* s2, size_t n);
static char_type* copy(char_type* s1,
const char_type* s2, size_t n);
static char_type* assign(char_type* s, size_t n,
char_type a);
static int_type not_eof(const int_type& c);
static char_type to_char_type(const int_type& c);
static int_type to_int_type(const char_type& c);
static bool eq_int_type(const int_type& c1,
const int_type& c2);
static int_type eof();
};
 

These functions are used by the basic_string class template for character-based operations common to string processing. When you declare a string variable, such as:

std::string s;
 

you are actually declaring s as follows (because of the default template arguments in the specification of basic_string):

std::basic_string<char, std::char_traits<char>,
std::allocator<char> > s;
 

Because the character traits have been separated from the basic_string class template, you can supply a custom traits class to replace std::char_traits. The following example illustrates this flexibility:

//: C05:BearCorner.h
#ifndef BEARCORNER_H
#define BEARCORNER_H
#include <iostream>
using std::ostream;
 
// Item classes (traits of guests):
class Milk {
public:
friend ostream& operator<<(ostream& os, const Milk&) {
return os << "Milk";
}
};
 
class CondensedMilk {
public:
friend ostream&
operator<<(ostream& os, const CondensedMilk &) {
return os << "Condensed Milk";
}
};
 
class Honey {
public:
friend ostream& operator<<(ostream& os, const Honey&) {
return os << "Honey";
}
};
 
class Cookies {
public:
friend ostream& operator<<(ostream& os, const Cookies&) {
return os << "Cookies";
}
};
 
// Guest classes:
class Bear {
public:
friend ostream& operator<<(ostream& os, const Bear&) {
return os << "Theodore";
}
};
 
class Boy {
public:
friend ostream& operator<<(ostream& os, const Boy&) {
return os << "Patrick";
}
};
 
// Primary traits template (empty-could hold common types)
template<class Guest> class GuestTraits;
 
// Traits specializations for Guest types
template<> class GuestTraits<Bear> {
public:
typedef CondensedMilk beverage_type;
typedef Honey snack_type;
};
 
template<> class GuestTraits<Boy> {
public:
typedef Milk beverage_type;
typedef Cookies snack_type;
};
#endif // BEARCORNER_H ///:~
 
//: C05:BearCorner.cpp
// Illustrates traits classes.
#include <iostream>
#include "BearCorner.h"
using namespace std;
 
// A custom traits class
class MixedUpTraits {
public:
typedef Milk beverage_type;
typedef Honey snack_type;
};
 
// The Guest template (uses a traits class)
template<class Guest, class traits = GuestTraits<Guest> >
class BearCorner {
Guest theGuest;
typedef typename traits::beverage_type beverage_type;
typedef typename traits::snack_type snack_type;
beverage_type bev;
snack_type snack;
public:
BearCorner(const Guest& g) : theGuest(g) {}
void entertain() {
cout << "Entertaining " << theGuest
<< " serving " << bev
<< " and " << snack << endl;
}
};
 
int main() {
Boy cr;
BearCorner<Boy> pc1(cr);
pc1.entertain();
Bear pb;
BearCorner<Bear> pc2(pb);
pc2.entertain();
BearCorner<Bear, MixedUpTraits> pc3(pb);
pc3.entertain();
} ///:~
 

In this program, instances of the guest classes Boy and Bear are served items appropriate to their tastes. Boys like milk and cookies, and Bears like condensed milk and honey. This association of guests to items is done via specializations of a primary (empty) traits class template. The default arguments to BearCorner ensure that guests get their proper items, but you can override this by simply providing a class that meets the requirements of the traits class, as we do with the MixedUpTraits class above. The output of this program is:

Entertaining Patrick serving Milk and Cookies
Entertaining Theodore serving Condensed Milk and Honey
Entertaining Theodore serving Milk and Honey
 

Using traits provides two key advantages: (1) it allows flexibility and extensibility in pairing objects with associated attributes or functionality, and (2) it keeps template parameter lists small and readable. If 30 types were associated with a guest, it would be inconvenient to have to specify all 30 arguments directly in each BearCorner declaration. Factoring the types into a separate traits class simplifies things considerably.

The traits technique is also used in implementing streams and locales, as we showed in Chapter 4. An example of iterator traits is found in the header file PrintSequence.h in Chapter 6.

Thinking in C++ Vol 2 - Practical Programming
Prev Home Next

 
 
   Reproduced courtesy of Bruce Eckel, MindView, Inc. Design by Interspire