Template Argument Deduction





Item 57. Template Argument Deduction

Class templates must be specialized explicitly. For example, if we want to specialize the Heap container discussed in Class Template Explicit Specialization [46, 155], we have to provide a type name argument to the template:

Heap<int> aHeap;
Heap<const char *> anotherHeap;

Function templates may also be specialized explicitly. Suppose we have a function template that performs a restricted old-style cast:

template <typename R, typename E>
R cast( const E &expr ) {
    // ...do some clever checking...
    return R( expr ); // ...and cast.
}

We may specialize the template explicitly when we call it, just as we must specialize a class template:

int a = cast<int,double>(12.3);

However, it's typical and more convenient to let the compiler deduce the template arguments from the types of the actual arguments to the function call. Not surprisingly, this process is called "template argument deduction." Careful! In the description below, pay attention to the difference between the terms "template argument" and "function argument" (see Template Terminology [45, 153]). Consider a template with a single template argument that finds the lesser of two function arguments.

template <typename T>
T min( const T &a, const T &b )
    { return a < b ? a : b; }

When we use min without supplying the template arguments explicitly, the compiler examines the types of the function call arguments in order to deduce the template argument:

int a = min( 12, 13 ); // T is int
double d = min( '\b', '\a' ); // T is char
char c = min( 12.3, 4 ); // error! T can't be both double and int

The erroneous line above is the result of the compiler's not being able to deduce a template argument in an ambiguous situation. In such cases, we can always tell the compiler what a template argument is by being explicit:

d = min<double>( 12.3, 4 ); // OK, T is double

A similar situation occurs with our cast template if we try to use template argument deduction:

int a = cast( 12.3 ); // error! E is double, but what's R?   

As with overload resolution, the compiler examines the types of only function arguments during template argument deduction, not potential return types. The only way the compiler's going to know the return type is if we tell it:

int a = cast<int>( 12.3 ); // E is double and
                           // R is (explicitly) int

Notice that any trailing template arguments may be left off the template argument list if the compiler can deduce them on its own. In this case we had only to supply the compiler with the return type and let it figure out the expression type on its own. The order of the template parameters is important for the template's usability, since if the expression type had preceded the return type, we would have had to specify both explicitly.

At this point, many people will notice the syntax of the call to cast above and ask, "Are you implying that static_cast, dynamic_cast, const_cast, and reinterpret_cast are function templates?" No, we're not implying that because these four cast operators are not templates, they're built-in operators (like the new operator or the + operator on integers); but it sure looks like their syntax was inspired by something similar to our cast function template. (See New Cast Operators [9, 29].)

Note that template argument deduction works by examining the types of the actual arguments to a call. This implies that any template argument of a function template that cannot be deduced from the argument types has to be supplied explicitly. For example, here's an annoyingly repetitious function template:

template <int n, typename T>
void repeat( const T &msg ) {
    for( int i = 0; i < n; ++i )
        std::cout << msg << std::flush;
}

We were careful to put the integer template argument before the type argument, so we could get by with specifying only the number of repetitions of the message, and let template argument deduction determine the type of the message:

repeat<12>( 42 ); // n is 12, T is int
repeat<MAXINT>( '\a' ); // n is big, T is char

In the cast, min, and repeat templates, the compiler deduced a single template argument from a single function argument. However, the deduction mechanism is capable of deducing multiple template arguments from the type of a single function argument:

template <int bound, typename T>
void zeroOut( T (&ary)[bound] ) {
    for( int i = 0; i < bound; ++i )
        ary[i] = T();
}
//...
const int hrsinweek = 7*24;
float readings[hrsinweek];
zeroOut( readings ); // bound == 168, T is float

In this case, zeroOut expects an array argument, and argument deduction is capable of dissecting the argument type to determine its bound and element type.

We noted at the start of this item that a class template must be specialized explicitly. However, function template argument deduction can be used to specialize a class template indirectly. Consider a class template that can be used to generate a function object from a function pointer (see STL Function Objects [20, 71]):

template <typename A1, typename A2, typename R>
class PFun2 : public std::binary_function<A1,A2,R> {
  public:
    explicit PFun2( R (*fp)(A1,A2) ) : fp_( fp ) {}
    R operator()( A1 a1, A2 a2 ) const
        { return fp_( a1, a2 ); }
  private:
    R (*fp_)(A1,A2);
};

(This is a simplified version of the standard pointer_to_binary_function template and has been chosen specifically for the convoluted syntax required to specialize it. It doesn't get much worse than this.) Instantiating the template directly is somewhat onerous:

bool isGreater( int, int );
std::sort(b, e, PFun2<int,int,bool>(isGreater)); // painful   

It's common in cases like this to provide a "helper function" whose only purpose is to deduce the template arguments in order to specialize, automagically, a class template:

template <typename R, typename A1, typename A2>
inline PFun2<A1,A2,R> makePFun( R (*pf)(A1,A2) )
    { return PFun2<A1,A2,R>(pf); }
//...
std::sort(b, e, makePFun(isGreater)); // much better...

In this deduction tour de force, the compiler is able to deduce both argument types and the return type from the type of a single function argument. This technique is commonly used in the standard library for utilities like ptr_fun, make_pair, mem_fun, back_inserter, and many others that are simply helper functions that ease the task of complex and error-prone class template specialization.


     Python   SQL   Java   php   Perl 
     game development   web development   internet   *nix   graphics   hardware 
     telecommunications   C++ 
     Flash   Active Directory   Windows