16.3. Overloading and Templates
AdvancedFunction 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.
INFO
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:
INFO
– If there is only one nontemplate function in the set of equally good matches, the nontemplate function is called.
INFO
– 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.
INFO
– Otherwise, the call is ambiguous.
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 ofdebug_rep
withT
bound tostring*
debug_rep(string*)
, which is the instantiation of the second version ofdebug_rep
withT
bound tostring
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 withT
bound toconst string*
debug_rep(const string*)
, the instantiation of the second version of the template withT
bound toconst 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.
INFO
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 string
s 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 withT
bound tostring
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.
INFO
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&)
, withT
bound tochar[10]
debug_rep(T*)
, withT
bound toconst char
debug_rep(const string&)
, which requires a conversion fromconst char*
tostring
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 string
s, 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&
.
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.
INFO
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.