Team LiB
Previous Section Next Section

16.3. Overloading and Templates

 
Image

Function templates can be overloaded by other templates or by ordinary, nontemplate functions. As usual, functions with the same name must differ either as to the number or the type(s) of their parameters.

 

Exercises Section 16.2.7

 

Exercise 16.47: Write your own version of the flip function and test it by calling functions that have lvalue and rvalue reference parameters.


 

Function matching (§ 6.4, p. 233) is affected by the presence of function templates in the following ways:

 

• The candidate functions for a call include any function-template instantiation for which template argument deduction (§ 16.2, p. 678) succeeds.

 

• The candidate function templates are always viable, because template argument deduction will have eliminated any templates that are not viable.

 

• As usual, the viable functions (template and nontemplate) are ranked by the conversions, if any, needed to make the call. Of course, the conversions used to call a function template are quite limited (§ 16.2.1, p. 679).

 

• Also as usual, if exactly one function provides a better match than any of the others, that function is selected. However, if there are several functions that provide an equally good match, then:

 

– If there is only one nontemplate function in the set of equally good matches, the nontemplate function is called.

 

– If there are no nontemplate functions in the set, but there are multiple function templates, and one of these templates is more specialized than any of the others, the more specialized function template is called.

 

– Otherwise, the call is ambiguous.

 

Image Warning

Correctly defining a set of overloaded function templates requires a good understanding of the relationship among types and of the restricted conversions applied to arguments in template functions.

 

 

Writing Overloaded Templates

 

As an example, we’ll build a set of functions that might be useful during debugging. We’ll name our debugging functions debug_rep, each of which will return a string representation of a given object. We’ll start by writing the most general version of this function as a template that takes a reference to a const object:

 

 

// print any type we don't otherwise handle
template <typename T> string debug_rep(const T &t)
{
    ostringstream ret; // see § 8.3 (p. 321)
    ret << t; // uses T's output operator to print a representation of t
    return ret.str(); // return a copy of the string to which ret is bound
}

 

This function can be used to generate a string corresponding to an object of any type that has an output operator.

 

Next, we’ll define a version of debug_rep to print pointers:

 

 

// print pointers as their pointer value, followed by the object to which the pointer points
// NB: this function will not work properly with char*; see § 16.3 (p. 698)
template <typename T> string debug_rep(T *p)
{
    ostringstream ret;
    ret << "pointer: " << p;         // print the pointer's own value
    if (p)
        ret << " " << debug_rep(*p); // print the value to which p points
    else
        ret << " null pointer";      // or indicate that the p is null
    return ret.str(); // return a copy of the string to which ret is bound
}

 

This version generates a string that contains the pointer’s own value and calls debug_rep to print the object to which that pointer points. Note that this function can’t be used to print character pointers, because the IO library defines a version of the << for char* values. That version of << assumes the pointer denotes a null-terminated character array, and prints the contents of the array, not its address. We’ll see in § 16.3 (p. 698) how to handle character pointers.

 

We might use these functions as follows:

 

 

string s("hi");
cout << debug_rep(s) << endl;

 

For this call, only the first version of debug_rep is viable. The second version of debug_rep requires a pointer parameter, and in this call we passed a nonpointer object. There is no way to instantiate a function template that expects a pointer type from a nonpointer argument, so argument deduction fails. Because there is only one viable function, that is the one that is called.

 

If we call debug_rep with a pointer:

 

 

cout << debug_rep(&s) << endl;

 

both functions generate viable instantiations:

 

debug_rep(const string* &), which is the instantiation of the first version of debug_rep with T bound to string*

 

debug_rep(string*), which is the instantiation of the second version of debug_rep with T bound to string

 

The instantiation of the second version of debug_rep is an exact match for this call. The instantiation of the first version requires a conversion of the plain pointer to a pointer to const. Normal function matching says we should prefer the second template, and indeed that is the one that is run.

 

Multiple Viable Templates

 

As another example, consider the following call:

 

 

const string *sp = &s;
cout << debug_rep(sp) << endl;

 

Here both templates are viable and both provide an exact match:

 

debug_rep(const string* &), the instantiation of the first version of the template with T bound to const string*

 

debug_rep(const string*), the instantiation of the second version of the template with T bound to const string

 

In this case, normal function matching can’t distinguish between these two calls. We might expect this call to be ambiguous. However, due to the special rule for overloaded function templates, this call resolves to debug_rep(T*), which is the more specialized template.

 

The reason for this rule is that without it, there would be no way to call the pointer version of debug_rep on a pointer to const. The problem is that the template debug_rep(const T&) can be called on essentially any type, including pointer types. That template is more general than debug_rep(T*), which can be called only on pointer types. Without this rule, calls that passed pointers to const would always be ambiguous.

 

Image Note

When there are several overloaded templates that provide an equally good match for a call, the most specialized version is preferred.

 

 

Nontemplate and Template Overloads

 

For our next example, we’ll define an ordinary nontemplate version of debug_rep to print strings inside double quotes:

 

 

// print strings inside double quotes
string debug_rep(const string &s)
{
    return '"' + s + '"';
}

 

Now, when we call debug_rep on a string,

 

 

string s("hi");
cout << debug_rep(s) << endl;

 

there are two equally good viable functions:

 

debug_rep<string>(const string&), the first template with T bound to string

 

debug_rep(const string&), the ordinary, nontemplate function

 

In this case, both functions have the same parameter list, so obviously, each function provides an equally good match for this call. However, the nontemplate version is selected. For the same reasons that the most specialized of equally good function templates is preferred, a nontemplate function is preferred over equally good match(es) to a function template.

 

Image Note

When a nontemplate function provides an equally good match for a call as a function template, the nontemplate version is preferred.

 

 

Overloaded Templates and Conversions

 

There’s one case we haven’t covered so far: pointers to C-style character strings and string literals. Now that we have a version of debug_rep that takes a string, we might expect that a call that passes character strings would match that version. However, consider this call:

 

 

cout << debug_rep("hi world!") << endl; // calls debug_rep(T*)

 

Here all three of the debug_rep functions are viable:

 

debug_rep(const T&), with T bound to char[10]

 

debug_rep(T*), with T bound to const char

 

debug_rep(const string&), which requires a conversion from const char* to string

 

Both templates provide an exact match to the argument—the second template requires a (permissible) conversion from array to pointer, and that conversion is considered as an exact match for function-matching purposes (§ 6.6.1, p. 245). The nontemplate version is viable but requires a user-defined conversion. That function is less good than an exact match, leaving the two templates as the possible functions to call. As before, the T* version is more specialized and is the one that will be selected.

 

If we want to handle character pointers as strings, we can define two more nontemplate overloads:

 

 

// convert the character pointers to string and call the string version of debug_rep
string debug_rep(char *p)
{
    return debug_rep(string(p));
}
string debug_rep(const char *p)
{
    return debug_rep(string(p));
}

 

Missing Declarations Can Cause the Program to Misbehave

 

It is worth noting that for the char* versions of debug_rep to work correctly, a declaration for debug_rep(const string&) must be in scope when these functions are defined. If not, the wrong version of debug_rep will be called:

 

 

template <typename T> string debug_rep(const T &t);
template <typename T> string debug_rep(T *p);
// the following declaration must be in scope
// for the definition of debug_rep(char*) to do the right thing
string debug_rep(const string &);
string debug_rep(char *p)
{
    // if the declaration for the version that takes a const string& is not in scope
    // the return will call debug_rep(const T&) with T instantiated to string
    return debug_rep(string(p));
}

 

Ordinarily, if we use a function that we forgot to declare, our code won’t compile. Not so with functions that overload a template function. If the compiler can instantiate the call from the template, then the missing declaration won’t matter. In this example, if we forget to declare the version of debug_rep that takes a string, the compiler will silently instantiate the template version that takes a const T&.

 

Image Tip

Declare every function in an overload set before you define any of the functions. That way you don’t have to worry whether the compiler will instantiate a call before it sees the function you intended to call.

 

 

Exercises Section 16.3

 

Exercise 16.48: Write your own versions of the debug_rep functions.

Exercise 16.49: Explain what happens in each of the following calls:

 

template <typename T> void f(T);
template <typename T> void f(const T*);
template <typename T> void g(T);
template <typename T> void g(T*);
int i = 42, *p = &i;
const int ci = 0, *p2 = &ci;
g(42);   g(p);   g(ci);   g(p2);
f(42);   f(p);   f(ci);   f(p2);

 

Exercise 16.50: Define the functions from the previous exercise so that they print an identifying message. Run the code from that exercise. If the calls behave differently from what you expected, make sure you understand why.


 
Team LiB
Previous Section Next Section