More C++ Idioms/Type Generator

From Wikibooks, open books for an open world
Jump to navigation Jump to search

Type Generator

[edit | edit source]

Intent

[edit | edit source]
  • To simplify creation of complex template-based types
  • To synthesize a new type or types based on template argument(s)
  • To localize default policies when policy-based class design is used

Also Known As

[edit | edit source]

Templated Typedef Idiom

Motivation

[edit | edit source]

Class templates designed using policy-based class design, often result in very flexible templates with multiple type parameters. One downside of such class templates is that too many type parameters must be provided when instantiating them. Default template parameters can help in such cases. However, when the last template parameter (policy class) differs from the default, all of the intermediate template parameters must be specified.

For example, consider a case in which special purpose allocators are used with standard C++ containers. The GNU C++ compiler provides many special purpose allocators in namespace __gnu_cxx as extensions of the standard C++ library. The following illustrates specializing std::map with GNU's malloc_allocator:

std::map <std::string, int, less<std::string>, __gnu_cxx::malloc_allocator<std::string>>

A variation of the above map using float instead of an int requires all the unrelated type parameters to be mentioned again.

std::map <std::string, float, less<std::string>, __gnu_cxx::malloc_allocator<std::string> >

The type generator idiom is used to reduce code bloat in such cases.

Solution and Sample Code

[edit | edit source]

In the type generator idiom, common (invariant) parts of a family of type definitions are collected together in a structure, whose sole purpose is to generate another type. For example, consider the Directory template shown below.

template <class Value>
struct Directory
{
  typedef std::map <std::string, Value, std::less<std::string>, 
                    __gnu_cxx::malloc_allocator<std::string> > type;
};

Directory<int>::type    // gives a map of string to integers.
Directory<float>::type  // gives a map of string to floats.

An extra level of indirection (struct Directory) is used to capture the invariant part and one or two template parameters are left open for customization. A type generator usually consolidates a complicated type expression into a simple one. A type generator can be used to generate more than one type by simply adding more typedefs.

For example, consider how standard STL algorithms are applied to maps.

Directory<int>::type age; // This is a map.
transform(age.begin(), age.end(),
          std::ostream_iterator<string>(std::cout, "\n"),
          _Select1st<std::map<std::string, int>::value_type> ());

An adapter that transforms map's value_type, which is a pair, into the first element of the pair. _Select1st does the job of adapter in the example above. Its type is needlessly complex with ample opportunity of typing it wrong when repeated multiple times. Instead, the type generator idiom simplifies type specification of the adapter considerably.

template <class Value>
struct Directory
{
  typedef map <string, Value, less<string>, __gnu_cxx::malloc_allocator<std::string> > type;
  typedef _Select1st<typename type::value_type> KeySelector;
  typedef _Select2nd<typename type::value_type> ValueSelector;
};
Directory<int>::type age;    // This is a map.
transform(age.begin(), age.end(),
          std::ostream_iterator<string>(std::cout, "\n"),
          Directory<int>::KeySelector());

Finally, the type generator idiom can be used to conveniently change the invariant type parameters, if needed. For example, changing malloc_allocator to debug_allocator throughout the program. The main reason why you might sometimes want to change it is to get more useful information from bounds-checking or leak-detection tools while debugging. Using type generators such a program-wide effect can be achieved by simply changing it at one place.

C++11

[edit | edit source]

C++11 added using and tried to make it the successor to typedef. Unlike typedef, using supports templates, eliminating the need to append a ::type. using is also harder to mess up, because typename is never necessary. The <type_traits> header has _t and _v versions of type traits since C++14, so instead of doing typename some_type_trait<Ts...>::type, you can do some_type_trait_t<Ts...>. If you previously wrote classes using the old idiom, you too can write helper alias templates that end with _t or _v.

// Old code kept for backwards compatibility
template <class Value>
struct Directory
{
  typedef map <string, Value, less<string>, __gnu_cxx::malloc_allocator<std::string> > type;
  typedef _Select1st<typename type::value_type> KeySelector;
  typedef _Select2nd<typename type::value_type> ValueSelector;
};

// What you should be using instead
template <class Value>
using Directory_t = typename Directory<Value>::type;
template <class Value>
using DirectoryKeySelector = typename Directory<Value>::KeySelector;
template <class Value>
using DirectoryValueSelector = typename Directory<Value>::ValueSelector;

// Code changed to use the new types
Directory_t<int> age; // No need to remember whether typename is necessary
transform(age.begin(), age.end(),
          std::ostream_iterator<string>(std::cout, "\n"),
          DirectoryKeySelector<int>()); // No need to remember whether typename is necessary

Known Uses

[edit | edit source]
  • Boost.Iterator library
  • The header <type_traits>
[edit | edit source]

References

[edit | edit source]

[1] Type Generator

[2] Policy Adaptors and the Boost Iterator Adaptor Library -- David Abrahams and Jeremy Siek

[3] Template Typedef -- Herb Sutter

[4] The New C++: Typedef Templates -- Herb Sutter