Templates vs. Overloading
Templates provide one way to have a family of functions with the same name. But so does overloading. How do the two things interact?
Oh, no, I hadn't even thought of that…
Let's take a look…
Look over this code, and see if you can figure out what it will do…
#include <iostream>
#include <string>
template <typename T>
void print(const T& val) {
std::cout << "templated print: " << val << std::endl;
}
void print(int val) {
std::cout << "int print: " << val << std::endl;
}
void print(double val) {
std::cout << "double print: " << val << std::endl;
}
int main()
{
print(42);
print(3.1415);
print("Hello");
return 0;
}
Once you've decided what you think will be printed (or what compiler errors you might see),
- Open in Online GDB to run the code online and see what happens.
The code prints
int print: 42
double print: 3.1415
templated print: Hello
So we can see that overloading and templates work well together. If there is an overloaded function the compiler can call, it will use that; if not, it will use the function template to stamp out a custom function for the given type.
Now we're going to remove the function that takes a double
. What do you think will happen? There are two possibilities we can imagine—the compiler will
- Perform type conversion to turn the
double
into anint
and use theint
version of the function; or,. - Use the template to stamp out a custom function that works on
double
s.
Which one do you think it'll do? Come to a conclusion, then click the print(double)
function (or comment it out) and rerun the program to see what actually happens.
It prints
int print: 42
templated print: 3.1415
templated print: Hello
We can see that C++ prefers to use the function template over performing type conversion.
So when it comes to choosing a function, a template match for an argument is considered better than a type conversion (or promotion), but worse than an exact match for the type.
As our final change, let's try adding this function:
void print(std::string val) {
std::cout << "string print: " << val << std::endl;
}
Once again, decide what you think will happen, and then run the code to see if it matches your expectations.
It still prints
int print: 42
templated print: 3.1415
templated print: Hello
I'm relieved that your answer matches mine, but I'm still super confused. Why isn't the compiler calling the
print()
function that takes astring
? Did we not define the function properly?You could investigate a bit more by seeing what happens if you leave the
string
version of the function alone, but comment out the function template.
If we comment out the function template, it does use our new function:
int print: 42
int print: 3
string print: Hello
Which tells us something interesting: that type conversion must be happening when we call print("Hello")
.
Woah! Woah! Isn't
"Hello"
astring
?No, it isn't, at least not a C++
std::string
. It is a C-style string, which has typeconst char*
, a pointer to the first character in an array of characters.
If you really want a genuine std::string
literal, rather than a C-style string, you need to say
using namespace std::string_literals;
print("Hello"s);
The s
on the end says we want a C++ std::string
. We almost never actually write this form, however, because the std::string
class provides automatic conversion from C-style strings to C++ ones.
Classes can provide automatic type conversion to/from other types?
Yes.
I want to know MORE about that!!
Okay…
(When logged in, completion status appears here.)