Class Template Specializations

In addition to specializing function templates, we can also specialize class templates. As an example, we’ll define a specialization of the library hash template that we can use to store Sales_data objects in an unordered container. By default, the unordered containers use hash<key_type>11.4, p. 444) to organize their elements. To use this default with our own data type, we must define a specialization of the hash template. A specialized hash class must define

• An overloaded call operator (§ 14.8, p. 571) that returns a size_t and takes an object of the container’s key type

• Two type members, result_type and argument_type, which are the return and argument types, respectively, of the call operator

• The default constructor and a copy-assignment operator (which can be implicitly defined (§ 13.1.2, p. 500))

The only complication in defining this hash specialization is that when we specialize a template, we must do so in the same namespace in which the original template is defined. We’ll have more to say about namespaces in § 18.2 (p. 785). For now, what we need to know is that we can add members to a namespace. To do so, we must first open the namespace:

// open the std namespace so we can specialize std::hash
namespace std {
}  // close the std namespace; note: no semicolon after the close curly

Any definitions that appear between the open and close curlies will be part of the std namespace.

The following defines a specialization of hash for Sales_data:

// open the std namespace so we can specialize std::hash
namespace std {
template <>           // we're defining a specialization with
struct hash<Sales_data> // the template parameter of Sales_data
{
    // the type used to hash an unordered container must define these types
    typedef size_t result_type;
    typedef Sales_data argument_type; // by default, this type needs ==
    size_t operator()(const Sales_data& s) const;
    // our class uses synthesized copy control and default constructor
};
size_t
hash<Sales_data>::operator()(const Sales_data& s) const
{
    return hash<string>()(s.bookNo) ^
           hash<unsigned>()(s.units_sold) ^
           hash<double>()(s.revenue);
}
} // close the std namespace; note: no semicolon after the close curly

Our hash<Sales_data> definition starts with template<>, which indicates that we are defining a fully specialized template. The template we’re specializing is named hash and the specialized version is hash<Sales_data>. The members of the class follow directly from the requirements for specializing hash.

As with any other class, we can define the members of a specialization inside the class or out of it, as we did here. The overloaded call operator must define a hashing function over the values of the given type. This function is required to return the same result every time it is called for a given value. A good hash function will (almost always) yield different results for objects that are not equal.

Here, we delegate the complexity of defining a good hash function to the library. The library defines specializations of the hash class for the built-in types and for many of the library types. We use an (unnamed) hash<string> object to generate a hash code for bookNo, an object of type hash<unsigned> to generate a hash from units_sold, and an object of type hash<double> to generate a hash from revenue. We exclusive OR4.8, p. 154) these results to form an overall hash code for the given Sales_data object.

It is worth noting that we defined our hash function to hash all three data members so that our hash function will be compatible with our definition of operator== for Sales_data14.3.1, p. 561). By default, the unordered containers use the specialization of hash that corresponds to the key_type along with the equality operator on the key type.

Assuming our specialization is in scope, it will be used automatically when we use Sales_data as a key to one of these containers:

// uses hash<Sales_data> and Sales_data operator==from § 14.3.1 (p. 561)
unordered_multiset<Sales_data> SDset;

Because hash<Sales_data> uses the private members of Sales_data, we must make this class a friend of Sales_data:

template <class T> class std::hash;  // needed for the friend declaration
class Sales_data {
friend class std::hash<Sales_data>;
    // other members as before
};

Here we say that the specific instantiation of hash<Sales_data> is a friend. Because that instantiation is defined in the std namespace, we must remember to that this hash type is defined in the std namespace. Hence, our friend declaration refers to std::hash.


Image Note

To enable users of Sales_data to use the specialization of hash, we should define this specialization in the Sales_data header.


..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset