Wednesday, September 26, 2007

Working around cv-qualifiers when specializing template classes

A great principle states that "libraries should be open to extension and closed to modification". This article tries to address an inconvenience when writing generic libraries that are extensible by the means of template specialization.

Imagine a generic class that implements a hashed set. It's signature would be something like:


template <class Key, class Hash = hash<Key>>
class hash_set;


Unless the user doesn't specify a hashing function explicitly the library uses a specialization of its 'hash' class. One such class would look something like:


template <class T>
struct hash
{
  size_t operator () (const T& x) const {
    return ???;
  }
};


Suddenly a problem occurs - we don't know how to hash every possible type. We'll just have to go with specializations and leave the unspecialized class undefined so that a compile-time error is raised if the hash doesn't know how to do so.


template <class T> struct hash;


Now, a concise library writer would provide specializations for the hash class for some basic types, so that the hash_set is immediately useful for simple cases.


template <> struct hash<char> {...}
template <> struct hash<short> {...}
template <> struct hash<int> {...}
template <> struct hash<char*> {...}
template <> struct hash<std::string> {...}
template <> struct hash<etc.> {...}


Now, our user can specialize the hash class for custom types and thus extend the library's reach.

As our users happily trek along different instantiations of the hash_set template suddenly a calamity strikes! Our user tries to instantiate a hash_set<const int>. Of course the compiler complains that no such specialization of hash<> exists. The user is discontent, the library vendor is in a stupor. A common scenario, really.

The tedious solution in such cases is to write a specialization for both const and non-const types. Yuck.

Luckily for us Boost provides a nice feature in its TypeTraits library - remove_const. Using it we can rewrite our hash_set as follows:


template <class Key, class Hash = hash<typename boost::remove_const<Key>::type>>
class hash_set;


Excellent. However, if our library vendor ever wants to write another class that uses a hash, for example a hash_map, they have to remember to add the remove_const bit. Each time. And what if our const remover happens to change?

Being smart about code responsibilities we come up with the following solution - the const remover will become a part of the hashing library, instead of the container library:


template <class T>
struct select_hash {
  typedef hash<typename boost::remove_const<T>::type> type;
};


or even better:


template <class T>
struct select_hash
  : hash<typename boost::remove_const<T>::type>
{};


Thus usage becomes:
template <class Key, class Hash = select_hash<Key>>
class hash_set;

Now we're set for clear sailing. Yet there is a way to be even cleverer, thus removing the need of select_hash and relying solely on hash. Observe as we change our unspecialized template:


template <class T>
struct hash
  : hash<typename boost::remove_const<T>::type>
{};


Now each instantiation of hash<> either stumbles on a perfect specialization (like hash<int>), or the generic one, that first tries to strip the const qualifier. Thus, a hash<const int> inherits hash<int> and it's a perfect circle. When used with a type that hash has no specialization for, the compiler raises an error that 'a class cannot inherit from itself'. We're done here.

Note: to remove the volatile qualifier as well use boost::remove_cv<>. Depending on your situation this may or may not be a useful move.

No comments: