We've seen how we can attempt to write generic functions
using preprocessor macros.
In this video will see how we can use c++ templates
to accomplish writing generic functions.
So what is a c++ template.
A template is it generic blueprint that the compiler uses
to generate specialized functions in classes.
As I just said, c++ supports function and class templates.
In this video, will learn about function templates,
and we'll see class templates in the next video.
The idea with templates is that we define a template with a placeholder type,
and then we plug in the actual type we want when we need it.
Then the compiler generates the specific function or class that we need.
Remember, all of this happens at compile time.
If you're coming from other programming languages,
you might be used to this happening at runtime,
c++ does it at compile time.
So we get the benefit of the compiler performing type checking for us
before the program executes.
C++ template supports the concept of generic programming
or metaprogramming
since we're providing a generic representation of a function of class
and then the compiler writes the actual function or class for us.
That's very, very powerful.
However, with power comes complexity.
C++ templates can be very very complex.
And while the concept is easy to understand,
seeing them in practice can sometimes be very intimidating,
even for experienced programmers.
Also, the error message is provided by the compiler can be very difficult to understand.
So, let's see how we can use function templates
with the max function that we used in the previous video.
Let's quickly review.
We want to write a max function that returns the maximum of 2 integers passed into it.
We can use a simple conditional expression, which, if you recall,
is equivalent to an if L statement.
Now we can simply use the max function in passing integers
and get back to max of those integers.
Now suppose we need a max function for doubles and another for characters.
And we could have another one for floats and long as you get the idea.
We could end up writing many of these max functions.
Just as we saw in the previous lecture, the code is the same for all of them.
It's the code with the conditional operator.
The only thing that changes are the types of the parameters.
So now let's see how we can use templates to allow to write just one blueprint
for this function.
Why don't we simply replace the type with an identifier
that represents any type.
In this case, we'll use the uppercase t,
but we can use any valid identifier we wish.
So that's it. That was easy. That's what we want, right?
Yes, indeed. But the computer won't know what to do with this, and it's going to give us a compiler error.
We need to explicitly tell the compiler that this is a template function.
So let's do that in the next slide.
Noticed that now we provide the compiler with the line template
and type name t and angle brackets.
That's the template parameter type.
This tells a computer the t
is the type name that will be replaced with whatever the user needs.
Now this will compile, but it will not generate any code.
Let me say that again.
This will not generate any code. It's simply a template or a blueprint.
Code is not generated by the compiler until the user uses a
specialized version of the template.
I'll show you how to do that as second.
But first,
I want you to know that we can use the reserved words
typed name or class in templates.
They are essentially equivalent.
I will use type name in this course, but you'll see
both type name and class in production code, so please be aware of that.
Okay. So now how do we use this template function.
Notice the code in the slide.
We declare 2 integers A&B and initialize them.
Then we call max with A and B as parameters.
But also noticed the template parameter we're using.
That's the int in angle brackets.
This gives the compiler all the information it needs to actually generate
a specialized function from the template we created
using int in place of t.
The syntax should look familiar.
We used it previously when we created vectors and smart pointers.
You guessed it vectors, unique pointer, shared pointers and so forth
are implemented as template classes.
Now suppose we need a max function for doubles.
Easy, we simply use max and pass into doubles,
say c and d and use a template parameter double in the angle brackets.
The compiler now knows that it needs to generate the double version
of the max function
off in the compiler can deduce the type of the template parameter,
and we don't even need to provide it as in the second statement.
How cool is that? That makes it even easier.
And we don't have to stop with doubles. If we need to compare 2 characters
and return the max character, we can simply use the template function max
with chars, and the compiler will generate the character version of the max function.
We can use almost any type we need.
Notice, I said almost. What do I mean by almost any type?
Well, let's take a closer look at the function again, and you'll see what I mean.
Notice that the code in the template function is using
the greater than operator to compare a and b.
This means that whatever type we use for t,
must support this operator.
For primitive types, like in int, in chars, in doubles, it's not a problem.
But for our own class types,
we have to be sure that our class overloads the greater than operator
or this won't compile.
And this example, we're assuming we have a player class,
and we're using max with player objects.
So the compiler will generate the max function that expects and compares
player objects.
Unless the player class overloads to greater than operator,
this code won't compile.
There is no limit to the number of template parameters you can have.
And of course, they can be of different types.
In this example, we're creating a function template for a function named func.
It expects 2 parameters. The first is a type t1
and the second is of type t2.
Notice that we specified to template parameters in this case.
So there's the function declaration. Now let's see how we would use it.
We can call func and explicit would provide an integer in the double in this example.
Or in the case of the cost of func with no template parameters,
the compiler will deduce the types from the function arguments.
That's it. Now, of course, we can pass by value by reference,
by pointer with const modifiers and so forth.
And all of the function parameters don't have to be generic.
You can see all the possibilities and combinations.
Now let's head over to the IDE and we'll write a few template functions.
Okay, so I'm in the section 20 workspace
in the FunctionTemplates project.
And what I'd like to do here is go over those 2 templates that we created in the slides,
and I'll show you how they work with integers, with doubles, with character
basically, with primitive times where there are no issues with overloading operators.
Then what we'll do is we'll create our own type, we'll call it person or something like that.
Then you'll see some of the issues that we have to address to get that
person class or structure in this case to work with these templates.
And then what we'll do is we'll create a swap templates function at the end.
All right. So let's go over what we've got here.
Now if you recall, we've got a function called min,
and it's a template function, right? So there's my temple a parameter it.
And basically, if you write this with int instead of ts, it's really, really easy,
and all you need to do is just replace it with the generic type t.
The compiler will take care of the rest.
So it's going to be in a of time t,
a b of type ts what it expects, and is going to return a type t.
And all we're doing is comparing, using less than. Now here's where the issue is.
If we're using integers here,
then -- assuming a and b are integers, then the less than
works great, right. We know how to compare integers and doubles and characters and so forth.
But when we use our own types, that's where the issue comes in.
I'll talk about that in a second.
But first let's just look at the real simple example of using it,
and then we'll look at this template function right here next.
Remember, at this point the compilers done nothing for us. It hasn't --
all it's done is make sure that this is valid,
but it has not created an integer version of this or
double version of this, nothing gets created. It's just a blueprint right now.
So what we've got here is right here on line 18,
you can see that I'm calling min
and I'm using the template parameter int that I'm passing into integers.
At this point, the compiler now sees this blueprint
and generates the integer version of it.
So t would be int now, right, so this hole template stuff gets taken away,
and we just get a function, just like we wrote in the slides with just plain integers.
So in this case, I expect that result to be a 2 because that's the minimum.
Now you'll also notice right here on line 19
that I'm not providing this template parameter right here
because here just min 2, 3.
The compilers smart enough to deduce that these are integers.
So it's going to create the integer version of it.
A lot of times the compiler. We'll figure it out.
Other times the compiler won't figure it out depending on how complex
the template function is.
So in this case, they can't figure it out. So all I need to do is just call min,
pass in it 2 or 3, and I'll get a 2, just like before.
Now here you can see I'm calling min with 2 characters, a and b.
In this case,they're literals.
So it doesn't really matter if they're literals or variables, along work the same.
The compiler will now see haha, 2 characters here.
So it would deduce that this template parameter right here is a character,
and it would generate the character version of that template function,
same will get an a right here when we display it.
Same thing one with the doubles here, 12.5 9.2,
a double version of the function will get created by the compiler.
And in very end here, we got integers, but
this is a real difference between the template functions and macros.
We don't have to worry about precedent for any thing here.
We just do what we need to do. We want 5 + 4.
We don't have to worry about c-style,
macros that we did before with a pound in the previous video.
This is going to work correctly because the compiler
knows c++.
So that's it. If we run this, we should get what we expect.
Let me build and run,
and here my results right here. Let me just scroll it up right here.
And that's just what we expected, a 2, a 2 and a, a 9.2 and 9.
What we'll do is we'll create our own structure.
Now this can be anything. It can be a class.
So I'm just going to create a structure. So I have to worry about private and public,
but it'll work exactly the same way with a class.
So right here, I'm going to create a structure, and I'm going to call a person.
And I'm just going to say that every person has a name,
which is a string, a c++ string,
and an age. That's it. There's my person -- let me spell struct correctly.
It's a structures, and I can create person objects, right.
So let's do that. And I'm going to do it up here right about here.
I'm going to say person p1.
And remember, I've got a string right here, name and I've got an age.
So my first person will be Curly.
And let's say that Curly is 50 years old.
And my second person
is Moe, and Moe is 30 years old.
And it keeps giving me that Curly, I'll get rid of that.
Okay. So now I've got 2 person objects.
So what I want now is I want the minimum of those 2, right.
Now I'll be able to use my min function that I wrote to be generic,
so it should expect any type that makes sense. Well here,
it doesn't quite make sense yet, but it will in a minute
because what I want to do is I want to compare their ages.
So let's just say a person p3
is the minimum of p1 and p2.
In other words, one with the least age. Now if we try to compile this,
we're going to get an error. In the error, if you can see that here,
it says error no match for operator less than
for the operand types person, person.
So you can see that the compiler has generated
that specialized function for the persons,
but now I can't compiled it because it doesn't know how to compare 2 persons using that less than. Okay.
So what do we do?
What -- we tell the compiler how do you -- how to do that?
So what we'll do is we'll simply write a real simple overloaded operator.
Remember, they return Boolean. The operator is operator
less than that's the one that's causing the problem.
And what does it expect? Well, remember, this is a member.
So we're going to say const person
ref right-hand side. Hope you guys remember this part of the
course,
and it's contst because I really don't want to modify anything here.
And all I'm doing here as you can just see I want to return
this
person's age,
is it less than the other person's age?
That's it. So if I run this now.
Now we've got it clean run, right.
We just need to display the result, which we haven't done yet. So I want to do that right here,
and I'll just say something like
std cout.
And I'll just say p3.name
is younger.
That's it.
So if we run this at this point, we should see Moe, right. We should say Moe is younger.
So let's run that.
There we go. Moe is younger. So let's change this around.
Now let's say that Curly is 15 and Moe is 30. We should say Curly is younger,
and that's exactly what's going on.
Okay. So that's pretty cool.
I'm able to use persons and integers, a doubles and all kinds of stuff, right.
And in this case, I'm really explicitly saying that
I'm interested in comparing ages here.
Okay. So now let's look at the second example right here.
In this one, we're using 2 template parameters. We're here on line 11.
Our function is called func. It expects
2 parameters, an b and b. A is of type t1,
b is of type t2. They can be the same or they can be different.
And all we're doing is just displaying a and b. That's it. Real, real simple.
So let's take a look at our main example down here.
There's a bunch of test cases here.
But you can see, in this case, I'm explicitly providing the template
parameters in that first example right here.
And you can see that I've got 2 ints,
and I am supplying those 2 ints. So we just expect this --
all this function does is display with the past into it. So it'll display 10 and 20.
Then we can do the same thing. But in this case,
the compiler is deducing the types. Notice, I'm not providing them explicitly.
Here I'm providing a character and a double explicitly.
Here I'm just letting the compiler deduce it.
And again, here's an integer, here's a c-string,
here's an integer, here's a c++ string.
So I am passing all kinds of stuff into this function.
And the compiler is going to generate the correct one for me, which is really cool.
So if I run this,
you could see down here, we're getting a 10 and the 20, the 10 and the 20 again
and the a and the 12.4 twice I'm getting that.
Then I've seen a 1000 and testing and 2000 and Frank, exactly what we expected.
Okay. So now let me scroll up here just before
line 31 right here.
And what I'd like to do here is let's call func
with a person or 2 persons, doesn't really matter. Remember.
It's expecting 2 different types or 2 types of the same.
So let's do that. Let's call func with p1
and p2.
Now when we try to compile here,
we're going to have a problem. It says error no match for the insertion operator.
That expects a stream, and if I come over here just a little bit and a person.
What's the problem? Well, the problem is that in this case
a is a person.
And there is no overloaded insertion operator for a person.
So that won't work.
So what we want to do is we want to add the overloaded insertion operator,
and we could do that really easily, we could do it right here.
We don't need a friend function here because this is all --
it's a structure, so everything is public. So we can do this. We could just say std
ostream. Remember this
and we're returning an ostream reference.
And what we're going to do here is its operator,
and we're doing the insertion operator.
The left-hand side is the stream.
And we don't want to do that const because we want to change it.
Now the right-hand side, this is the person. So in this case, it's a const
person reference, let me just call it p.
So what do we want to put on the string? Well, we could just say os and let's say it's p's name,
that makes it really easy. And we want to return the stream.
That's it. We run this now.
Now we get Curly and Moe.
Notice, they're both printing out right here.
That's exactly what we wanted.
So this is one of the things that will get you when you're first starting out with template functions
is yes,I can supply any kind of type to it for sure.
But a lot of times when using operators,
you've got to really be careful with those operators to make sure that they were correctly,
overload them if you have to.
Okay. So now the last thing I want to do is let's create our another
template function, and we'll create it right down here right before the main.
And what we'll do is we'll just call it my swamp.
And I just want to swap 2 elements, right.
So how when we do this with an int. I would say something like int --
oh, sorry, that would do something like void, and we'll call it my swap.
It expects 2 integers, now we want to change them.
So we really want them by reference.
And here's the second one.
And then in here, all we're doing is a signing a temporary, right. So we have an int temp
equals a, then a equals b
and then b equals temp, right, simple swamp,
simple simple logic.
That'll work. That'll swap 2 integers.
But we don't just want to swap integers, we want to swap anything.
So what we can do here is we can come to this int
and replace it with the t, replace this guy with a t
and replace that guy with a t. And remember that could be any
later, I'm just using t.
So now we'll tell the compiler that this is a template function,
and its type name is t.
That's it. Now we've got a template function called my swamp.
That's pretty cool.
Now I'll do it appear. So we'll see that is the first output.
And what we can do here so we could just say int x is 100,
int y is 200.
And we can swap them, right. So that's a x and y. If we displayed x and y,
we obviously get 100 200. But now we can call my swamp,
and we can pass in x and y.
And at this point
what I want to do is just display x and y. So I'll copy that int here real quick.
That's it. So it's going to display x and y. What I expect now is
200 100, right.
Actually you know what, let me put this before and after the swap,
that way it'll be really, really obvious what's going on.
That's it, right here. So if I run this now,
you can see 100 200 at the top, and then 200 100, which
swap them.
Now the cool thing about that I can write
this with strings, characters, all kinds of good stuff, right.
And I'll leave that to you guys to play because this video is getting a little bit long.
Try replacing some of these with your own types and playing around with it. You'll see it's
not so bad. Once you understand the basics of template functions,
you'll see how powerful they are.
But remember, those little gotches.If you get those errors
that are typically about operators that
the compiler can't resolve. So it's your job to resolve them.
Okay. So that's it for template functions.
In the next video, we'll start talking about template classes.