8.3 Template Arguments

Ru-Brd

Template arguments are the "values" that are substituted for template parameters when instantiating a template. These values can be determined using several different mechanisms:

  • Explicit template arguments: A template name can be followed by explicit template argument values enclosed in angle brackets. The resulting name is called a template-id .

  • Injected class name: Within the scope of a class template X with template parameters P1 , P2 , , the name of that template ( X ) can be equivalent to the template-id X<P1, P2, > . See Section 9.2.3 on page 126 for details.

  • Default template arguments: Explicit template arguments can be omitted from class template instances if default template arguments are available. However, even if all template parameters have a default value, the (possibly empty) angle brackets must be provided.

  • Argument deduction : Function template arguments that are not explicitly specified may be deduced from the types of the function call arguments in a call. This is described in detail in Chapter 11. Deduction is also done in a few other situations. If all the template arguments can be deduced , no angle brackets need to be specified after the name of the function template.

8.3.1 Function Template Arguments

Template arguments for a function template can be specified explicitly or deduced from the way the template is used. For example:

  // details/max.cpp  template <typename T>  inline T const& max (T const& a, T const& b)  {      return a<b?b:a;  }  int main()  {      max<double>(1.0, -3.0);  // explicitly specify template argument  max(1.0, -3.0);  // template argument is implicitly deduced   // to be  double      max<int>(1.0, 3.0);  // the explicit  <int>  inhibits the deduction;   // hence the result has type  int  } 

Some template arguments can never be deduced (see Chapter 11). The corresponding parameters are best placed at the beginning of the list of template parameters so they can be specified explicitly while allowing the other arguments to be deduced. For example:

  // details/implicit.cpp  template <typename DstT, typename SrcT>  inline DstT implicit_cast (SrcT const& x)  //  SrcT  can be deduced,  {  // but  DstT  cannot  return x;  }  int main()  {      double value = implicit_cast<double>(-1);  } 

If we had reversed the order of the template parameters in this example (in other words, if we had written template<typename SrcT, typename DstT> ), a call of implicit_cast would have to specify both template arguments explicitly.

Because function templates can be overloaded, explicitly providing all the arguments for a function template may not be sufficient to identify a single function: In some cases, it identifies a set of functions. The following example illustrates a consequence of this observation:

 template <typename Func, typename T>  void apply (Func func_ptr, T x)  {      func_ptr(x);  }  template <typename T> void single(T);  template <typename T> void multi(T);  template <typename T> void multi(T*);  int main()  {      apply(&single<int>, 3);  // OK  apply(&multi<int>, 7);  // ERROR: no single  multi<int>  } 

In this example, the first call to apply() works because the type of the expression &single<int> is unambiguous. As a result, the template argument value for the Func parameter is easily deduced. In the second call, however, &multi<int> could be one of two different types and therefore Func cannot be deduced in this case.

Furthermore, it is possible that explicitly specifying the template arguments for a function template results in an attempt to construct an invalid C++ type. Consider the following overloaded function template ( RT1 and RT2 are unspecified types):

 template<typename T> RT1 test(typename T::X const*);  template<typename T> RT2 test(...); 

The expression test<int> makes no sense for the first of the two function templates because type int has no member type X . However, the second template has no such problem. Therefore, the expression &test<int> identifies the address of a single function. The fact that the substitution of int into the first template fails does not make the expression invalid.

This "substitution-failure-is-not-an-error" (SFINAE) principle is clearly an important ingredient to make the overloading of function templates practical. However, it also enables remarkable compile-time techniques. For example, assuming that types RT1 and RT2 are defined as follows :

 typedef char RT1;  typedef struct { char a[2]; } RT2; 

We can check at compile time (in other words, as a so-called constant-expression ) whether a given type T has a member type X :

 #define type_has_member_type_X(T)    \          (sizeof(test<T>(0)) == 1) 

To understand the expression in this macro, it is convenient to analyze from the outside to the inside. First, the sizeof expression will equal one if the first test template (which returns a char of size one) is selected. The other template returns a structure with a size that is at least two (because it contains an array of size two). In other words, this is a device to determine as a constant-expression whether the first or second template was selected for the call test<T>(0) . Clearly, the first template cannot be selected if the given type T has no member type X . However, if the given type has a member type X , then the first template is preferred because overload resolution (see Appendix B) prefers the conversion from zero to a null pointer constant over binding an argument to an ellipsis parameter (ellipsis parameters are the weakest kind of binding from an overload resolution perspective). Similar techniques are explored in Chapter 15.

The SFINAE principle protects only against attempts to create invalid types but not against attempts to evaluate invalid expressions. The following example is therefore invalid C++:

 template<int I> void f(int (&)[24/(4-I)]);  template<int I> void f(int (&)[24/(4+I)]);  int main()  {      &f<4>;  // ERROR: division by zero (SFINAE doesn't apply)  } 

This example is an error even though the second template supports the substitution without leading to a division by zero. This sort of error must occur in the expression itself and not in binding of an expression to a template parameter. Indeed, the following example is valid:

 template<int N> int g() { return N; }  template<int* P> int g() { return *P }  int main()  {      return g<1>();  //  1  cannot be bound to  int*  parameter,  }  // but SFINAE principle applies  

See Section 15.2.2 on page 266 and Section 19.3 on page 353 for further applications of the SFINAE principle.

8.3.2 Type Arguments

Template type arguments are the "values" specified for template type parameters. Most commonly used types can be used as template arguments, but there are two exceptions:

  1. Local classes and enumerations (in other words, types declared in a function definition) cannot be involved in template type arguments.

  2. Types that involve unnamed class types or unnamed enumeration types cannot be template type arguments (unnamed classes or enumerations that are given a name through a typedef declaration are OK).

An example illustrates these two exceptions:

 template <typename T> class List {   };  typedef struct {      double x, y, z;  } Point;  typedef enum { red, green, blue } *ColorPtr;  int main()  {      struct Association      {          int* p;          int* q;      };      List<Assocation*> error1;  // ERROR: local type in template argument  List<ColorPtr> error2;  // ERROR: unnamed type in template   //        argument  List<Point> ok;  // OK: unnamed class type named through   //     a typedef  } 

Although other types can, in general, be used as template arguments, their substitution for the template parameters must lead to valid constructs:

 template <typename T>  void clear (T p)  {      *p = 0;  // requires that the unary  *  be applicable to  T  }  int main()  {      int a;      clear(a);  // ERROR:  int  doesn't support the unary  *  } 

8.3.3 Nontype Arguments

Nontype template arguments are the values substituted for nontype parameters. Such a value must be one of the following things:

  • Another nontype template parameter that has the right type

  • A compile-time constant value of integer (or enumeration) type. This is acceptable only if the corresponding parameter has a type that matches that of the value, or a type to which the value can be implicitly converted (for example, a char can be provided for an int parameter).

  • The name of an external variable or function preceded by the built-in unary & ("address of") operator. For functions and array variables , & can be left out. Such template arguments match nontype parameters of a pointer type.

  • The previous kind of argument but without a leading & operator is a valid argument for a nontype parameter of reference type.

  • A pointer-to-member constant; in other words, an expression of the form &C::m where C is a class type and m is a nonstatic member (data or function). This matches nontype parameters of pointer-to-member type only.

When matching an argument to a parameter that is a pointer or reference, user -defined conversions (constructors for one argument and conversion operators) and derived-to-base conversions are not considered , even though in other circumstances they would be valid implicit conversions. Implicit conversions that make an argument more const or more volatile are fine.

Here are some valid examples of nontype template arguments:

 template <typename T, T nontype_param>  class C;  C<int, 33>* c1;  // integer type  int a;  C<int*, &a>* c2;  // address of an external variable  void f();  void f(int);  C<void (*)(int), &f>* c3;  // name of a function: overload resolution selects   //  f(int)  in this case; the  &  is implied  class X {      int n;      static bool b;  };  C<bool&, X::b>* c4;  // static class members are acceptable variable   // and function names  C<int X::*, &X::n>* c5;  // an example of a pointer-to-member constant  template<typename T>  void templ_func();  C<void (), &templ_func<double> >* c6;  // function template instantiations are functions too  

A general constraint of template arguments is that a compiler or a linker must be able to express their value when the program is being built. Values that aren't known until a program is run (for example, the address of local variables) aren't compatible with the notion that templates are instantiated when the program is built.

Even so, there are some constant values that are, perhaps surprisingly, not currently valid:

  • Null pointer constants

  • Floating-point numbers

  • String literals

One of the problems with string literals is that two identical literals can be stored at two distinct addresses. An alternative (but cumbersome) way to express templates instantiated over constant strings involves introducing an additional variable to hold the string:

 template <char const* str>  class Message;  extern char const hello[] = "Hello World!";  Message<hello>* hello_msg; 

Note the need for the extern keyword because otherwise a const array variable would have internal linkage.

See Section 4.3 on page 40 for another example and Section 13.4 on page 209 for a discussion of possible future changes in this area.

Here are few other (less surprising) invalid examples:

 template<typename T, T nontype_param>  class C;  class Base {      int i;  } base;  class Derived : public Base {  } derived_obj;  C<Base*, &derived_obj>* err1;  // ERROR: derived-to-base conversions are   //        not considered  C<int&, base.i>* err2;  // ERROR: fields of variables aren't   //        considered to be variables  int a[10];  C<int*, &a[0]>* err3;  // ERROR: addresses of individual array   //        elements aren't acceptable either  

8.3.4 Template Template Arguments

A template template argument must be a class template with parameters that exactly match the parameters of the template template parameter it substitutes. Default template arguments of a template template argument are ignored (but if the template template parameter has default arguments, they are considered during the instantiation of the template).

This makes the following example invalid:

 #include <list>  // declares:   //  namespace std {  //  template <typename T,  //  typename Allocator = allocator<T> >  //  class list;  //  }  template<typename T1,           typename T2,           template<typename> class Container>  //  Container  expects templates with only   // one parameter  class Relation {    public:   private:      Container<T1> dom1;      Container<T2> dom2;  };  int main()  {      Relation<int, double, std::list> rel;  // ERROR:  std::list  has more than one template parameter    } 

The problem in this example is that the std::list template of the standard library has more than one parameter. The second parameter (which describes a so-called allocator ) has a default value, but this is not considered when matching std::list to the Container parameter.

Sometimes, such situations can be worked around by adding a parameter with a default value to the template template parameter. In the case of the previous example, we may rewrite the Relation template as follows:

 #include <memory>  template<typename T1,           typename T2,           template<typename T,                    typename = std::allocator<T> > class Container>  //  Container  now accepts standard container templates  class Relation {    public:   private:      Container<T1> dom1;      Container<T2> dom2;  }; 

Clearly this isn't entirely satisfactory, but it enables the use of standard container templates. Section 13.5 on page 211 discusses possible future changes of this topic.

The fact that syntactically only the keyword class can be used to declare a template template parameter is not to be construed as an indication that only class templates declared with the keyword class are allowed as substituting arguments. Indeed, "struct templates" and "union templates" are valid arguments for a template template parameter. This is similar to the observation that (just about) any type can be used as an argument for a template type parameter declared with the keyword class .

8.3.5 Equivalence

Two sets of template arguments are equivalent when values of the arguments are identical one-for-one. For type arguments, typedef names don't matter: It is the type ultimately underlying the typedef that is compared. For integer nontype arguments, the value of the argument is compared; how that value is expressed doesn't matter. The following example illustrates this concept:

 template <typename T, int I>  class Mix;  typedef int Int;  Mix<int, 3*3>* p1;  Mix<Int, 4+5>* p2;  //  p2  has the same type as  p1 

A function generated from a function template is never equivalent to an ordinary function even though they may have the same type and the same name. This has two important consequences for class members:

  1. A function generated from a member function template never overrides a virtual function.

  2. A constructor generated from a constructor template is never a default copy constructor. (Similarly, an assignment generated from an assignment template is never a copy-assignment operator. However, this is less prone to problems because unlike copy constructors, assignment operators are never called implicitly.)

Ru-Brd


C++ Templates
C++ Templates: The Complete Guide
ISBN: 0201734842
EAN: 2147483647
Year: 2002
Pages: 185

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net