Making It Easier to Use std::type_info as an Index in an Associative Container

Doc. no.:N2530=08-0040
Date:2008-02-03
Author:James Kanze
email:james.kanze@gmail.com


Motivation and scope

One of the most frequent uses of std::type_info is as an index in a associative container, for example, mapping a type identifier to a factory function. Currently, this is more difficult than necessary for the associative containers defined in [associative], and impossible to do reliably for the unordered containers defined in [unord]. This proposal consists of two parts: the first to add support for unordered containers to std::type_info itself, and the second to provide a simple wrapper class which can be used directly as an index type in any of the associative containers (ordered or unordered).

Adding support for unordered containers to std::type_info

Proposed text

In [type.info], in the class definition:

    namespace std {
      class type_info {
      public:
        virtual ~type_info();
        bool operator==(const type_info& rhs) const;
        bool operator!=(const type_info& rhs) const;
        bool before(const type_info& rhs) const;
        size_t hash_code() const throw();
        const char* name() const;
        type_info(const type_info& rhs) = delete; // cannot be copied
        type_info& operator=(const type_info& rhs) = delete; // cannot be copied
      };
    }
  

In [type.info], after paragraph 6, add the following paragraphs:

size_t hash_code() const throw

Returns: an unspecified value, except that within a single execution of the program, two type_info which compare equal should return equal values.

Remarks: as far as possible, an implementation should attempt to return different values when the type_info it is called on do not compare equal.

Rationale

It was not felt necessary (nor particularly easy to implement) to require that the hash value be the same across different program runs. This means that it cannot be used for external hashing, e.g. in a file. This is not really a constraint, however, since the type_info cannot be write to or read from an external source either. If some sort of mapping or type identity is needed in an external source, the user must define his own identity type, and can define a hash code for it if needed.

The added function, hash_code must be nothrow if it is to be used in a specialization of hash, since [unord.hash] says that the operator() shall not throw exceptions. (For that matter, is there any reason to allow operator==, operator!= and below to throw?)

Implementation considerations

A trivial implementation would simply calculate a hash code over the return value of name(). Implementations should be encouraged to do better: for example, if the implementation guarantees that there is only one instance of the type_info for each type (typical for Unix implementations, I think), then it could simply convert the address into a size_t (using reinterpret_cast).

Class type_index

Proposed text

After [type.info], add a section [type.index], as follows:

18.6.2 Class type_index

    namespace std {
      class type_index {
      public:
        type_index( const type_info& rhs );
        bool operator==( const type_index& rhs ) const;
        bool operator!=( const type_index& rhs ) const;
        bool operator< (const type_index& rhs ) const;
        bool operator<= (const type_index& rhs ) const;
        bool operator> (const type_index& rhs ) const;
        bool operator>= (const type_index& rhs ) const;
        size_t hash_code() const;
        const char* name() const;
      private:
        // const type_info* target;    //  exposition only.
        // Note that the use of a pointer here, rather than a reference,
        // means that the default copy constructor and assignment
        // operator will be provided and work as expected.
      };

      template<>
      struct hash< type_index > : public std::unary_function< type_index, size_t > {
        size_t operator()( type_index index ) const;
      }
    }
  

The class type_index provides a simple wrapper for type_info which can be used as an index type in associative containers [associative] and in unordered associative containers [unord].

18.6.2.1 type_index member functions

type_index::type_index( const type_info& rhs )

Effects: constructs a type_index, the equivalent of: target = &rhs ;

bool operator==(const type_index& rhs) const;

Returns: *target == *rhs.target

bool operator!=(const type_index& rhs) const;

Returns: *target != *rhs.target

bool operator<(const type_index& rhs) const;

Returns: target->before( *rhs.target )

bool operator<=(const type_index& rhs) const;

Returns: ! rhs.target->before( *target )

bool operator>(const type_index& rhs) const;

Returns: rhs.target->before( *target )

bool operator>=(const type_index& rhs) const;

Returns: ! target->before( *rhs.target )

size_t hash_code() const;

Returns: target->hash_code()

const char* name() const;

Returns: target->name()

18.6.2.2 template specialization hash<type_index>

size_t operator()( type_index index ) const;

Returns: index.hash_code()

Rationale

Obviously, a user could easily implement something like this himself. Still, it seems to cover a common use case, in which it seems preferable that users not be required to reinvent the wheel each time. I have something very similar in my own library—the main difference is that it uses the naming conventions of my library, and not those of the standard—and I'm certain that many others do as well.

Strictly speaking, I don't see any need or use for name(), but given that the class presents itself as a fassade, there doesn't seem any justifiable reason to not provide it either. (For that matter, I don't see any real need for any of the relation operators except <, but the rule of least surprise suggests that if one is present, they all are.)