www.forum.vingrad.ru - форумы по программированию. Программирование на С++
Задать вопрос на форуме по C++


[35] Templates  Updated! , C++ FAQ Lite

[35] Templates  Updated! 
(Part of C++ FAQ Lite, Copyright © 1991-2005, Marshall Cline, cline@parashift.com)


FAQs in section [35]:


[35.1] What's the idea behind templates?

A template is a cookie-cutter that specifies how to cut cookies that all look pretty much the same (although the cookies can be made of various kinds of dough, they'll all have the same basic shape). In the same way, a class template is a cookie cutter for a description of how to build a family of classes that all look basically the same, and a function template describes how to build a family of similar looking functions.

Class templates are often used to build type safe containers (although this only scratches the surface for how they can be used).

TopBottomPrevious sectionNext sectionSearch the FAQ ]


[35.2] What's the syntax / semantics for a "class template"?

Consider a container class Array that acts like an array of integers:

 // This would go into a header file such as "Array.h"
 class Array {
 public:
   Array(int len=10)                  : len_(len), data_(new int[len]) { }
  ~Array()                            { delete[] data_; }
   int len() const                    { return len_;     }
   const int& operator[](int i) const { return data_[check(i)]; }  
 subscript operators often come in pairs
         int& operator[](int i)       { return data_[check(i)]; }  
 subscript operators often come in pairs
   Array(const Array&);
   Array& operator= (const Array&);
 private:
   int  len_;
   int* data_;
   int  check(int i) const
     { if (i < 0 || i >= len_) throw BoundsViol("Array", i, len_);
       return i; }
 };

Repeating the above over and over for Array of float, of char, of std::string, of Array-of-std::string, etc, will become tedious.

 // This would go into a header file such as "Array.h"
 template<typename T>
 class Array {
 public:
   Array(int len=10)                : len_(len), data_(new T[len]) { }
  ~Array()                          { delete[] data_; }
   int len() const                  { return len_;     }
   const T& operator[](int i) const { return data_[check(i)]; }
         T& operator[](int i)       { return data_[check(i)]; }
   Array(const Array<T>&);
   Array<T>& operator= (const Array<T>&);
 private:
   int len_;
   T*  data_;
   int check(int i) const
     { if (i < 0 || i >= len_) throw BoundsViol("Array", i, len_);
       return i; }
 };

Unlike template functions, template classes (instantiations of class templates) need to be explicit about the parameters over which they are instantiating:

 int main()
 {
   Array<int>           ai;
   Array<float>         af;
   Array<char*>         ac;
   Array<std::string>   as;
   Array< Array<int> >  aai;
   
...
 }

Note the space between the two >'s in the last example. Without this space, the compiler would see a >> (right-shift) token instead of two >'s.

TopBottomPrevious sectionNext sectionSearch the FAQ ]


[35.3] What's the syntax / semantics for a "function template"?

Consider this function that swaps its two integer arguments:

 void swap(int& x, int& y)
 {
   int tmp = x;
   x = y;
   y = tmp;
 }

If we also had to swap floats, longs, Strings, Sets, and FileSystems, we'd get pretty tired of coding lines that look almost identical except for the type. Mindless repetition is an ideal job for a computer, hence a function template:

 template<typename T>
 void swap(T& x, T& y)
 {
   T tmp = x;
   x = y;
   y = tmp;
 }

Every time we used swap() with a given pair of types, the compiler will go to the above definition and will create yet another "template function" as an instantiation of the above. E.g.,

 int main()
 {
   int         i,j;  
/*...*/  swap(i,j);  // Instantiates a swap for int
   float       a,b;  
/*...*/  swap(a,b);  // Instantiates a swap for float
   char        c,d;  
/*...*/  swap(c,d);  // Instantiates a swap for char
   std::string s,t;  
/*...*/  swap(s,t);  // Instantiates a swap for std::string
   
...
 }

Note: A "template function" is the instantiation of a "function template".

TopBottomPrevious sectionNext sectionSearch the FAQ ]


[35.4] How do I explicitly select which version of a function template should get called?

When you call a function template, the compiler tries to deduce the template type. Most of the time it can do that successfully, but every once in a while you may want to help the compiler deduce the right type — either because it cannot deduce the type at all, or perhaps because it would deduce the wrong type.

For example, you might be calling a function template that doesn't have any parameters of its template argument types, or you might want to force the compiler to do certain promotions on the arguments before selecting the correct function template. In these cases you'll need to explicitly tell the compiler which instantiation of the function template should be called.

Here is a sample function template where the template parameter T does not appear in the function's parameter list. In this case the compiler cannot deduce the template parameter types when the function is called.

 template<typename T>
 void f()
 {
   
...
 }

To call this function with T being an int or a std::string, you could say:

 #include <string>
 
 void sample()
 {
   f<int>();          
// type T will be int in this call
   f<std::string>();  
// type T will be std::string in this call
 }

Here is another function whose template parameters appear in the function's list of formal parameters (that is, the compiler can deduce the template type from the actual arguments):

 template<typename T>
 void g(T x)
 {
   
...
 }

Now if you want to force the actual arguments to be promoted before the compiler deduces the template type, you can use the above technique. E.g., if you simply called g(42) you would get g<int>(42), but if you wanted to pass 42 to g<long>(), you could say this: g<long>(42). (Of course you could also promote the parameter explicitly, such as either g(long(42)) or even g(42L), but that ruins the example.)

Similarly if you said g("xyz") you'd end up calling g<char*>(char*), but if you wanted to call the std::string version of g<>() you could say g<std::string>("xyz"). (Again you could also promote the argument, such as g(std::string("xyz")), but that's another story.)

TopBottomPrevious sectionNext sectionSearch the FAQ ]


[35.5] What is a "parameterized type"?

Another way to say, "class templates."

A parameterized type is a type that is parameterized over another type or some value. List<int> is a type (List) parameterized over another type (int).

TopBottomPrevious sectionNext sectionSearch the FAQ ]


[35.6] What is "genericity"?

Yet another way to say, "class templates."

Not to be confused with "generality" (which just means avoiding solutions which are overly specific), "genericity" means class templates.

TopBottomPrevious sectionNext sectionSearch the FAQ ]


[35.7] Why can't I separate the definition of my templates class from it's declaration and put it inside a .cpp file?

If all you want to know is how to fix this situation, read the next FAQ. But in order to understand why things are the way they are, first accept these facts:

  1. A template is not a class or a function. A template is a "pattern" that the compiler uses to generate a family of classes or functions.
  2. In order for the compiler to generate the code, it must see both the template definition (not just declaration) and the specific types/whatever used to "fill in" the template. For example, if you're trying to use a Foo<int>, the compiler must see both the Foo template and the fact that you're trying to make a specific Foo<int>.
  3. Your compiler probably doesn't remember the details of one .cpp file while it is compiling another .cpp file. It could, but most do not and if you are reading this FAQ, it almost definitely does not. BTW this is called the "separate compilation model."

Now based on those facts, here's an example that shows why things are the way they are. Suppose you have a template Foo defined like this:

 template<typename T>
 class Foo {
 public:
   Foo();
   void someMethod(T x);
 private:
   T x;
 };

Along with similar definitions for the member functions:

 template<typename T>
 Foo<T>::Foo()
 {
   
...
 }
 
 template<typename T>
 void Foo<T>::someMethod(T x)
 {
   
...
 }

Now suppose you have some code in file Bar.cpp that uses Foo<int>:

 // Bar.cpp
 
 void blah_blah_blah()
 {
   
...
   Foo<int> f;
   f.someMethod(5);
   
...
 }

Clearly somebody somewhere is going to have to use the "pattern" for the constructor definition and for the someMethod() definition and instantiate those when T is actually int. But if you had put the definition of the constructor and someMethod() into file Foo.cpp, the compiler would see the template code when it compiled Foo.cpp and it would see Foo<int> when it compiled Bar.cpp, but there would never be a time when it saw both the template code and Foo<int>. So by rule #2 above, it could never generate the code for Foo<int>::someMethod().

A note to the experts: I have obviously made several simplifications above. This was intentional so please don't complain too loudly. If you know the difference between a .cpp file and a compilation unit, the difference between a class template and a template class, and the fact that templates really aren't just glorified macros, then don't complain: this particular question/answer wasn't aimed at you to begin with. I simplified things so newbies would "get it," even if doing so offends some experts.

TopBottomPrevious sectionNext sectionSearch the FAQ ]


[35.8] How can I avoid linker errors with my template functions?

Tell your C++ compiler which instantiations to make while it is compiling your template function's .cpp file.

As an example, consider the header file foo.h which contains the following template function declaration:

 // file "foo.h"
 template<typename T>
 extern void foo();

Now suppose file foo.cpp actually defines that template function:

 // file "foo.cpp"
 #include <iostream>
 #include "foo.h"
 
 template<typename T>
 void foo()
 {
   std::cout << "Here I am!\n";
 }

Suppose file main.cpp uses this template function by calling foo<int>():

 // file "main.cpp"
 #include "foo.h"
 
 int main()
 {
   foo<int>();
   
...
 }

If you compile and (try to) link these two .cpp files, most compilers will generate linker errors. There are three solutions for this. The first solution is to physically move the definition of the template function into the .h file, even if it is not an inline function. This solution may (or may not!) cause significant code bloat, meaning your executable size may increase dramatically (or, if your compiler is smart enough, may not; try it and see).

The other solution is to leave the definition of the template function in the .cpp file and simply add the line template void foo<int>(); to that file:

 // file "foo.cpp"
 #include <iostream>
 #include "foo.h"
 
 template<typename T> void foo()
 {
   std::cout << "Here I am!\n";
 }
 
 template void foo<int>();

If you can't modify foo.cpp, simply create a new .cpp file such as foo-impl.cpp as follows:

 // file "foo-impl.cpp"
 #include "foo.cpp"
 
 template void foo<int>();

Notice that foo-impl.cpp #includes a .cpp file, not a .h file. If that's confusing, click your heels twice, think of Kansas, and repeat after me, "I will do it anyway even though it's confusing." You can trust me on this one. But if you don't trust me or are simply curious, the rationale is given earlier.

If you are using Comeau C++, you probably want to check out the export keyword.

TopBottomPrevious sectionNext sectionSearch the FAQ ]


[35.9] How can I avoid linker errors with my template classes?

Tell your C++ compiler which instantiations to make while it is compiling your template class's .cpp file.

(If you've already read the previous FAQ, this answer is completely symmetric with that one, so you can probably skip this answer.)

As an example, consider the header file Foo.h which contains the following template class. Note that method Foo<T>::f() is inline and methods Foo<T>::g() and Foo<T>::h() are not.

 // file "Foo.h"
 template<typename T>
 class Foo {
 public:
   void f();
   void g();
   void h();
 };
 
 template<typename T>
 inline
 void Foo<T>::f()
 {
   
...
 }

Now suppose file Foo.cpp actually defines the non-inline methods Foo<T>::g() and Foo<T>::h():

 // file "Foo.cpp"
 #include <iostream>
 #include "Foo.h"
 
 template<typename T>
 void Foo<T>::g()
 {
   std::cout << "Foo<T>::g()\n";
 }
 
 template<typename T>
 void Foo<T>::h()
 {
   std::cout << "Foo<T>::h()\n";
 }

Suppose file main.cpp uses this template class by creating a Foo<int> and calling its methods:

 // file "main.cpp"
 #include "Foo.h"
 
 int main()
 {
   Foo<int> x;
   x.f();
   x.g();
   x.h();
   
...
 }

If you compile and (try to) link these two .cpp files, most compilers will generate linker errors. There are three solutions for this. The first solution is to physically move the definition of the template functions into the .h file, even if they are not inline functions. This solution may (or may not!) cause significant code bloat, meaning your executable size may increase dramatically (or, if your compiler is smart enough, may not; try it and see).

The other solution is to leave the definition of the template function in the .cpp file and simply add the line template class Foo<int>; to that file:

 // file "Foo.cpp"
 #include <iostream>
 #include "Foo.h"
 
 
...definition of Foo<T>::f() is unchanged -- see above...
 
...definition of Foo<T>::g() is unchanged -- see above...
 
 template class Foo<int>;

If you can't modify Foo.cpp, simply create a new .cpp file such as Foo-impl.cpp as follows:

 // file "Foo-impl.cpp"
 #include "Foo.cpp"
 
 template class Foo<int>;

Notice that Foo-impl.cpp #includes a .cpp file, not a .h file. If that's confusing, click your heels twice, think of Kansas, and repeat after me, "I will do it anyway even though it's confusing." You can trust me on this one. But if you don't trust me or are simply curious, the rationale is given earlier.

If you are using Comeau C++, you probably want to check out the export keyword.

TopBottomPrevious sectionNext sectionSearch the FAQ ]


[35.10] Why do I get linker errors when I use template friends?

Ah, the intricacies of template friends. Here's an example of what people often want to do:

 #include <iostream>
 
 template<typename T>
 class Foo {
 public:
   Foo(const T& value = T());
   friend Foo<T> operator+ (const Foo<T>& lhs, const Foo<T>& rhs);
   friend std::ostream& operator<< (std::ostream& o, const Foo<T>& x);
 private:
   T value_;
 };

Naturally the template will need to actually be used somewhere:

 int main()
 {
   Foo<int> lhs(1);
   Foo<int> rhs(2);
   Foo<int> result = lhs + rhs;
   std::cout << result;
   
...
 }

And of course the various member and friend functions will need to be defined somewhere:

 template<typename T>
 Foo<T>::Foo(const T& value = T())
   : value_(value)
 { }
 
 template<typename T>
 Foo<T> operator+ (const Foo<T>& lhs, const Foo<T>& rhs)
 { return Foo<T>(lhs.value_ + rhs.value_); }
 
 template<typename T>
 std::ostream& operator<< (std::ostream& o, const Foo<T>& x)
 { return o << x.value_; }

The snag happens when the compiler sees the friend lines way up in the class definition proper. At that moment it does not yet know the friend functions are themselves templates; it assumes they are non-templates like this:

 Foo<int> operator+ (const Foo<int>& lhs, const Foo<int>& rhs)
 { ... }
 
 std::ostream& operator<< (std::ostream& o, const Foo<int>& x)
 { ... }

When you call the operator+ or operator<< functions, this assumption causes the compiler to generate a call to the non-template functions, but the linker will give you an "undefined external" error because you never actually defined those non-template functions.

The solution is to convince the compiler while it is examining the class body proper that the operator+ and operator<< functions are themselves templates. There are several ways to do this; one simple approach is pre-declare each template friend function above the definition of template class Foo:

 template<typename T> class Foo;  // pre-declare the template class itself
 template<typename T> Foo<T> operator+ (const Foo<T>& lhs, const Foo<T>& rhs);
 template<typename T> std::ostream& operator<< (std::ostream& o, const Foo<T>& x);

Also you add <> in the friend lines, as shown:

 #include <iostream>
 
 template<typename T>
 class Foo {
 public:
   Foo(const T& value = T());
   friend Foo<T> operator+ <> (const Foo<T>& lhs, const Foo<T>& rhs);
   friend std::ostream& operator<< <> (std::ostream& o, const Foo<T>& x);
 private:
   T value_;
 };

After the compiler sees that magic stuff, it will be better informed about the friend functions. In particular, it will realize that the friend lines are referring to functions that are themselves templates. That eliminates the confusion.

Another approach is to define the friend function within the class body at the same moment you declare it to be a friend. For example:

 #include <iostream>
 
 template<typename T>
 class Foo {
 public:
   Foo(const T& value = T());
 
   friend Foo<T> operator+ (const Foo<T>& lhs, const Foo<T>& rhs)
   {
     
...
   }
 
   friend std::ostream& operator<< (std::ostream& o, const Foo<T>& x)
   {
     
...
   }
 
 private:
   T value_;
 };

TopBottomPrevious sectionNext sectionSearch the FAQ ]


[35.11] How can any human hope to understand these overly verbose template-based error messages?

Here's a free tool that transforms error messages into something more understandable. At the time of this writing, it works with the following compilers: Comeau C++, Intel C++, CodeWarrior C++, gcc, Borland C++, Microsoft Visual C++, and EDG C++.

Here's an example showing some unfiltered gcc error messages:

 rtmap.cpp: In function `int main()':
 rtmap.cpp:19: invalid conversion from `int' to `
    std::_Rb_tree_node<std::pair<const int, double> >*'
 rtmap.cpp:19:   initializing argument 1 of `std::_Rb_tree_iterator<_Val, _Ref,
    _Ptr>::_Rb_tree_iterator(std::_Rb_tree_node<_Val>*) [with _Val =
    std::pair<const int, double>, _Ref = std::pair<const int, double>&, _Ptr =
    std::pair<const int, double>*]'
 rtmap.cpp:20: invalid conversion from `int' to `
    std::_Rb_tree_node<std::pair<const int, double> >*'
 rtmap.cpp:20:   initializing argument 1 of `std::_Rb_tree_iterator<_Val, _Ref,
    _Ptr>::_Rb_tree_iterator(std::_Rb_tree_node<_Val>*) [with _Val =
    std::pair<const int, double>, _Ref = std::pair<const int, double>&, _Ptr =
    std::pair<const int, double>*]'
 E:/GCC3/include/c++/3.2/bits/stl_tree.h: In member function `void
    std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::insert_unique(_II,
     _II) [with _InputIterator = int, _Key = int, _Val = std::pair<const int,
    double>, _KeyOfValue = std::_Select1st<std::pair<const int, double> >,
    _Compare = std::less<int>, _Alloc = std::allocator<std::pair<const int,
    double> >]':
 E:/GCC3/include/c++/3.2/bits/stl_map.h:272:   instantiated from `void std::map<_
 Key, _Tp, _Compare, _Alloc>::insert(_InputIterator, _InputIterator) [with _Input
 Iterator = int, _Key = int, _Tp = double, _Compare = std::less<int>, _Alloc = st
 d::allocator<std::pair<const int, double> >]'
 rtmap.cpp:21:   instantiated from here
 E:/GCC3/include/c++/3.2/bits/stl_tree.h:1161: invalid type argument of `unary *
    '

Here's what the filtered error messages look like (note: you can configure the tool so it shows more information; this output was generated with settings to strip things down to a minimum):

 rtmap.cpp: In function `int main()':
 rtmap.cpp:19: invalid conversion from `int' to `iter'
 rtmap.cpp:19:   initializing argument 1 of `iter(iter)'
 rtmap.cpp:20: invalid conversion from `int' to `iter'
 rtmap.cpp:20:   initializing argument 1 of `iter(iter)'
 stl_tree.h: In member function `void map<int,double>::insert_unique(_II, _II)':
     [STL Decryptor: Suppressed 1 more STL standard header message]
 rtmap.cpp:21:   instantiated from here
 stl_tree.h:1161: invalid type argument of `unary *'

Here is the source code to generate the above example:

 #include <map>
 #include <algorithm>
 #include <cmath>
 
 const int values[] = { 1,2,3,4,5 };
 const int NVALS = sizeof values / sizeof (int);
 
 int main()
 {
     using namespace std;
 
     typedef map<int, double> valmap;
 
     valmap m;
 
     for (int i = 0; i < NVALS; i++)
         m.insert(make_pair(values[i], pow(values[i], .5)));
 
     valmap::iterator it = 100;              
// error
     valmap::iterator it2(100);              
// error
     m.insert(1,2);                          
// error
 
     return 0;
 }

TopBottomPrevious sectionNext sectionSearch the FAQ ]


[35.12] Why am I getting errors when my template-derived-class accesses something it inherited from its template-base-class?

Perhaps surprisingly, the following code is not valid C++, even though some compilers accept it:

 template<typename T>
 class B {
 public:
   void f() { }
 };
 
 template<typename T>
 class D : public B<T> {
 public:
   void g()
   {
     f();  
 compiler might give an error here
   }
 };

This might hurt your head; better if you sit down.

Within D<T>::g(), the name f does not depend on template parameter T, so f is known as a nondependent name. B<T>, on the other hand, is dependent on template parameter T so B<T> is called a dependent name.

Here's the rule: the compiler does not look in dependent base classes (like B<T>) when looking up nondependent names (like f).

This doesn't mean that inheritance doesn't work. Class D<int> is still derived from class B<int>, the compiler still lets you implicitly do the is-a conversions (e.g., D<int>* to B<int>*), dynamic binding still works when virtual functions are invoked, etc. But there is an issue about how names are looked up.

Workarounds:

TopBottomPrevious sectionNext sectionSearch the FAQ ]


[35.13] Can the previous problem hurt me silently? Is it possible that the compiler will silently generate the wrong code?  Updated! 

[Recently fixed a typo thanks to Assaf Lavie (in 5/05). Click here to go to the next FAQ in the "chain" of recent changes.]

Yes.

Since the non-dependent name f is not looked up in the dependent base-class B<T>, the compiler will search the enclosing scope (such as the enclosing namespace) for the name f. This can cause it to silently(!) do the wrong thing.

For example:

 void f() { }  // a global ("namespace scope") function
 
 template<typename T>
 class B {
 public:
   void f() { }
 };
 
 template<typename T>
 class D : public B<T> {
 public:
   void g()
   {
     f();
   }
 };

Here the call within D<T>::g() will silently(!) call ::f() rather than B<T>::f().

You have been warned.

TopBottomPrevious sectionNext sectionSearch the FAQ ]


E-Mail E-mail the author
C++ FAQ LiteTable of contentsSubject indexAbout the author©Download your own copy ]
Revised Jun 1, 2005 Права на материал принадлежат их авторам
Вернуться на главную страницу



www.forum.vingrad.ru - форумы по программированию. Программирование на С++
Задать вопрос на форуме по C++