Выбрать главу

                                allocator<charT> >();

}

int main() {

  bitset<10> bs;

  bs.set(1);

  bs.set(5);

  cout << bs << endl; // 0000100010

  string s = bitsetToString<char>(bs);

  cout << s << endl;  // 0000100010

} ///:~

The bitset class supports conversion to string object via its to_string member function. To support multiple string classes, to_string is itself a template, following the pattern established by the basic_string template discussed in Chapter 3. The declaration of to_string inside of bitset looks like this:.

template <class charT, class traits, class Allocator>

basic_string<charT, traits, Allocator> to_string() const;

Our bitsetToString( ) function template above allows you to request different types of string representations of a bitset. To get a wide string, for instance, you change the call to the following:

  wstring s = bitsetToString<wchar_t>(bs);

Note that basic_string uses default template arguments, so we don’t have to repeat the char_traits and allocator arguments in the return value. Unfortunately, bitset::to_string does not use default arguments. Using bitsetToString<char>( bs) is more convenient than typing a fully-qualified call to bs.template to_string<char, char_traits, allocator<char> >( ) every time.

The return statement in bitsetToString( ) contains the template keyword in an odd place—right after the dot operator applied to the bitset object bs. This is because when the template is parsed, the < character after the to_string token would be interpreted as a less-than operation instead of the beginning or a template argument list. We explain exactly why this confusion exists in the section "Name lookup issues" later in this chapter. The template keyword used in this context tells the compiler that what follows is the name of a template, causing the < character to be interpreted correctly. The same reasoning applies to the -> and :: operators when applied to templates. As with the typename keyword, this template disambiguation technique can only be used within a template.[50] 

Member Templates

The bitset::to_string( ) function template is an example of a member template: a template declared within another class or class template. This allows many combinations of independent template arguments to be combined. A useful example is found in the complex class template in the standard C+ library. The complex template has a type parameter meant to represent an underlying floating-point type to hold the real and imaginary parts of a complex number. The following code snippet from the standard library shows a member-template constructor in the complex class template:.

template<typename T>

class complex {

public:

  template<class X> complex(const complex<X>&);

The standard complex template comes ready-made with specializations that use float, double, and long double for the parameter T. The member-template constructor above allows you to create a new complex number that uses a different floating-point type as its base type, as seen in the code below:.

  complex<float> z(1,2);

  complex<double> w(z);

In the declaration of w, the complex template parameter T is double and X is float. Member templates make this kind of flexible conversion easy.

Since defining a template within a template is a nesting operation, the prefixes that introduce the templates must reflect that nesting if you define the member template outside the outer class definition. For example, if you were to implement the complex class template, and if you were to define the member-template constructor above outside the complex template class definition, you would have to do it like this:.

template<typename T>

template<typename X>

complex<T>::complex(const complex<X>& c) {/*body here…*/}

Another use of member function templates in the standard library is in the initialization of containers, such as a vector. Suppose we have a vector of ints and we want to initialize a new vector of doubles with it, like this:.

  int data[5] = {1,2,3,4,5};

  vector<int> v1(data, data+5);

  vector<double> v2(v1.begin(), v1.end());

As long as the elements in v1 are assignment-compatible with the elements in v2 (as double and int are here), all is well. The vector class template has the following member template constructor:.

template <class InputIterator>

vector(InputIterator first, InputIterator last,

       const Allocator& = Allocator());

This constructor is actually used twice in the vector declarations above. When v1 is initialized from the array of ints, the type InputIterator is int*. When v2 is initialized from v1, an instance of the member template constructor is used with InputIterator representing vector<int>::iterator.

Member templates can also be classes. (They don’t have to be functions, although that’s usually what you need.) The following example shows a member class template inside an outer class template.

//: C05:MemberClass.cpp

// A member class template

#include <iostream>

#include <typeinfo>

using namespace std;

template<class T>

class Outer {

public:

  template<class R>

  class Inner {

  public:

    void f();

  };

};

template<class T> template <class R>

void Outer<T>::Inner<R>::f() {

  cout << "Outer == " << typeid(T).name() << endl;

  cout << "Inner == " << typeid(R).name() << endl;

  cout << "Full Inner == " << typeid(*this).name() << endl;

}

int main() {

  Outer<int>::Inner<bool> inner;

  inner.f();

} ///:~

The typeid operator, which is covered in Chapter 8, returns an object whose name( ) member function yields a string representation of a type or of the type of a variable. Although the exact representation varies from compiler to compiler, the output of the program above should be something like this:.