Team LiB   Previous Section   Next Section

7.3 Function Templates

A function template defines a pattern for any number of functions whose definitions depend on the template parameters. You can overload a function template with a non-template function or with other function templates. You can even have a function template and a non-template function with the same name and parameters.

Function templates are used throughout the standard library. The best-known function templates are the standard algorithms (such as copy, sort, and for_each).

Declare a function template using a template declaration header followed by a function declaration or definition. Default template arguments are not allowed in a function template declaration or definition. In the following example, round is a template declaration for a function that rounds a number of type T to N decimal places (see example 7-14 for the definition of round), and min is a template definition for a function that returns the minimum of two objects:

// Round off a floating-point value of type T to N digits.
template<unsigned N, typename T>  T round(T x);
// Return the minimum of a and b.
template<typename T> T min(T a, T b)
{ return a < b ? a : b; }

To use a function template, you must specify a template instance by providing arguments for each template parameter. You can do this explicitly, by listing arguments inside angle brackets:

long x = min<long>(10, 20);

However, it is more common to let the compiler deduce the template argument types from the types of the function arguments:

int x = min(10, 20); // Calls min<int>(  )

See Section 7.3.2 later in this chapter for details.

7.3.1 Function Signatures

The signature of a function template includes the template parameters even if those parameters are never used in the function's return type or parameter types. Thus, instances of different template specializations produce distinct functions. The following example has two function templates, both named func. Because their template parameters differ, the two lines declare two separate function templates:

template<typename T> void func(T x = T(  ));
template<typename T, typename U> void func(T x = U(  ));

Typically, such function templates are instantiated in separate files; otherwise, you must specify the template arguments explicitly to avoid overload conflicts.

The function signature also includes expressions that use any of the template parameters. In the following example, two function templates overload func. The function parameter type is an instance of the demo class template. The template parameter for demo is an expression that depends on func's template parameter (T or i):

template<int x> struct demo {};
template<typename T> void func(demo<sizeof(T)>);
template<int i> void func(demo<i+42>);

Just as you can have multiple declarations (but not definitions) of a single function, you can declare the same function template multiple times using expressions that differ only by the template parameter names. Even if the declarations are in separate files, the compiler and linker ensure that the program ends up with a single copy of each template instance.

If the expressions differ by more than just the parameter names, they result in distinct functions, so you can have distinct function template definitions. If the argument expressions result in the same value, the program is invalid, although the compiler is not required to issue an error message:

template<int i> void func(demo<i+42>);
template<int x> void func(demo<x+42>) {}   // OK
template<int i> void func(demo<i+41>) {}   // OK
template<int i> void func(demo<i+40+2>) {} // Error

Follow the standard procedure of declaring templates in header files and using those headers in the source files where they are needed, thereby ensuring that every source file sees the same template declarations. Then you will not have to be concerned about different source files seeing declarations that differ only by the form of the template argument expressions. The compiler tries to ensure that the program ends up with a single copy of functions that are meant to be the same, but distinct copies of those that are meant to be distinct.

7.3.2 Deducing Argument Types

Most uses of function templates do not explicitly specify all of the template arguments, letting the compiler deduce the template arguments from the function call. You can provide explicit arguments for the leftmost template parameters, leaving the rightmost parameters for the compiler to deduce. If you omit all the template arguments, you can also choose to omit the angle brackets.

In the following discussion, make sure you distinguish between the parameters and arguments of a template and those of a function. Argument type deduction is the process of determining the template arguments, given a function call or other use of a function (such as taking the address of a function).

The basic principle is that a function call has a list of function arguments, in which each function argument has a type. The function argument types must be matched to function parameter types; in the case of a function template, the function parameter types can depend on the template parameters. The ultimate goal, therefore, is to determine the appropriate template arguments that let the compiler match the function parameter types with the given function argument types.

Usually, the template parameters are type or template template parameters, but in a few cases, value parameters can also be used (as the argument for another template or as the size of an array).

The function parameter type can depend on the template parameter in the following ways, in which the template parameter is v for a value parameter, T for a type parameter, or TT for a template template parameter:

  • T, T*, T&, const T, volatile T, const volatile T

  • Array of T

  • Array of size v

  • Template TT, or any class template whose template argument is T or v

  • Function pointer in which T is the return type or the type of any function parameter

  • Pointer to a data member of T, or to a data member whose type is T

  • Pointer to a member function that returns T, to a member function of T, or to a member function in which T is the type of any function parameter

Types can be composed from any of these forms. Thus, type deduction can occur for a struct in which the types of its members are also deduced, or a function's return type and parameter types can be deduced.

A single function argument can result in the deduction of multiple template parameters, or multiple function arguments might depend on a single template parameter.

If the compiler cannot deduce all the template arguments of a function template, you must provide explicit arguments. Example 7-3 shows several different ways the compiler can deduce template arguments.

Example 7-3. Deducing template arguments
#include <algorithm>
#include <cstddef>
#include <iostream>
#include <list>
#include <ostream>
#include <set>

// Simple function template. Easy to deduce T.
template<typename T>
T min(T a, T b)
{
  return a < b ? a : b;
}

template<typename T, std::size_t Size>
struct array
{
  T value[Size];
};

// Deduce the row size of a 2D array.
template<typename T, std::size_t Size>
std::size_t dim2(T [][Size])
{
  return Size;
}

// Overload the function size(  ) to return the number of elements in an array<>
// or in a standard container.
template<typename T, std::size_t Size>
std::size_t size(const array<T,Size>&)
{
  return Size;
}

template<template<typename T, typename A> class container,
         typename T, typename A>
typename container<T,A>::size_type size(container<T,A>& c)
{
  return c.size(  );
}

template<template<typename T, typename C, typename A>
           class container,
         typename T, typename C, typename A>
typename container<T,C,A>::size_type
size(container<T,C,A>& c)
{
  return c.size(  );
}

// More complicated function template. Easy to deduce Src, but impossible to
// deduce Dst.
template<typename Dst, typename Src>
Dst cast(Src s)
{
  return s;
}

int main(  )
{
  min(10, 100);      // min<int>
  min(10, 1000L);    // Error: T cannot be int and long

  array<int,100> data1;
  // Deduce T=int and SIZE=100 from type of data1.
  std::cout << size(data1) << '\n';

  int y[10][20];
  std::cout << dim2(y) << '\n';  // Prints 20

  std::list<int> lst;
  lst.push_back(10);
  lst.push_back(20);
  lst.push_back(10);
  std::cout << size(lst) << '\n';  // Prints 3

  std::set<char> s;
  const char text[] = "hello";              
  std::copy(text, text+5, std::inserter(s, s.begin(  )));
  std::cout << size(s) << '\n';  // Prints 4

  // Can deduce Src=int, but not return type, so explicitly set Dst=char
  char c = cast<char>(42);
}

7.3.3 Overloading

Function templates can be overloaded with other function templates and with non-template functions. Templates introduce some complexities, however, beyond those of ordinary overloading (as covered in Chapter 5).

As with any function call, the compiler collects a list of functions that are candidates for overload resolution. If the function name matches that of a function template, the compiler tries to determine a set of template arguments, deduced or explicit. It the compiler cannot find any suitable template arguments, the template is not considered for overload resolution. If the compiler can find a set of template arguments, they are used to instantiate the function template, and the template instance is added to the list of candidate functions. Thus, each template contributes at most one function to the list of overload candidates. The best match is then chosen from the list of template and non-template functions according to the normal rules of overload resolution: non-template functions are preferred over template functions, and more specialized template functions are preferred over less specialized template functions. See Chapter 5 for more details on overload resolution.

Example 7-4 shows how templates affect overload resolution. Sometimes, template instantiation issues can be subtle. The last call to print results in an error, not because the compiler cannot instantiate print, but because the instantiation contains an error, namely, that operator << is not defined for std::vector<int>.

Example 7-4. Overloading functions and function templates
#include <cctype>
#include <iomanip>
#include <iostream>
#include <ostream>
#include <string>
#include <vector>

template<typename T>
void print(const T& x)
{
  std::cout << x;
}

void print(char c)
{
  if (std::isprint(c))
    std::cout << c;
  else
    std::cout << std::hex << std::setfill('0')
              << "'\\x" << std::setw(2) << int(c) << '\''
              << std::dec;
}

template<>
void print(const long& x)
{
  std::cout << x << 'L';
}

int main(  )
{
  print(42L);    // Calls print<long> specialization
  print('x');    // Calls print(char) overload
  print('\0');    // Calls print(char) overload
  print(42);     // Calls print<int>
  print(std::string("y"));     // Calls print<string>
  print(  );                   // Error: argument required
  print(std::vector<int>(  )); // Error
}

7.3.4 Operators

You can create operator templates in the same manner as function templates. The usual rules for deducing argument types apply as described earlier in this section. If you want to specify the arguments explicitly, you can use the operator keyword:

template<typename T>
bigint operator+(const bigint& x, const T& y);
bigint a;
a = a + 42;                    // Argument type deduction
a = operator+<long>(a, 42);    // Explicit argument type

When using operator<, be sure to leave a space between the operator symbol and the angle brackets that surround the template argument, or else the compiler will think you are using the left-shift operator:

operator<<long>(a, 42);  // Error: looks like left-shift
operator< <long>(a, 42); // OK

Type conversion operators can use ordinary type cast syntax, but if you insist on using an explicit operator keyword, you cannot use the usual syntax for specifying the template argument. Instead, the target type specifies the template argument type:

struct demo {
  template<typename T>
  operator T*(  ) const { return 0; }
};
demo d;
char* p = d.operator int*(  );                // Error: illegal cast
char* c = d; // d.operator char*(  );         // OK
    Team LiB   Previous Section   Next Section