Hello everyone.
In the last video we learned about UN scoped enumerations, that is enumerations whose enumerators are
unqualified and they are visible throughout the scope in which the enumerator was declared.
In this video, we'll learn about the other side of the coin scoped enumerations, that is, enumerations
whose enumerators are qualified, and therefore they're only visible by using the scope resolution operator.
This means that if we want to access an enumerations enumerators, we must specify which enumeration
the enumerator belongs to.
In place of the singular enum, a keyword used to declare a scoped enumerations.
We declare scoped enumerations using the enum class key.
We can also use enum struct and they are both semantically equivalent.
So you're probably wondering why do we even need scoped enumerations?
Well, you may remember me saying that scoped enumerations can sometimes cause issues that can only
be resolved by using scoped enumerations.
So let's take a look at two motivating examples so you can understand why.
Here we can see two UN scoped enumerations.
The first one contains species of whales and the other species of sharks.
Now, suppose we want to determine whether a beluga whale is equivalent to a hammerhead shark.
Because both enumerations are un scoped.
This is a valid comparison and even worse it evaluate to true since both the numerators have the same
underlying value of one.
Obviously this is not what we want since a beluga whale is certainly not equivalent to a hammerhead
shark.
Another issue that can arise from the use of UN scoped enumerations are name clashes.
Suppose that we want to include the blue shark species in our shark enumeration.
You'll notice that it has the same enumerator name as the blue whale species because both enumerations
are un scoped.
The compiler will think we're trying to re declare the enumerator blue and it will generate an error.
Of course, we could just rename the enumerator to something more specific, but in general, we don't
want to have to worry about naming our enumerators.
So what can we do to address this issue?
As you've probably already guessed, we can declare the enumerations as scoped enumerations.
The use of if and switch statements with scoped enumerations is identical to that of scoped, but with
the additional requirement of specifying enumerator scopes.
In fact, everything you can do with an scoped enumeration, you can do with a scoped enumeration so
long as you specify the scope of its enumerators.
With that said, there is one difference that's worth discussing.
In this example, we have a scoped enumeration type named item that might be items in a grocery or shopping
list.
The underlying value of each item might be an item code or some other value that makes sense in our
application.
You'll remember that with un scoped enumerations if we wanted to use their enumerator values, we simply
stated the name of the enumerator and the compiler would implicitly convert its value to its underlying
type.
With scoped enumerations.
That's not possible.
In fact, if we try to do so, even with specifying the numerator scope, the compiler will generate
an error.
This is because scoped enumeration type variables are not implicitly convertible.
Instead, we must explicitly cast a variable to its underlying type.
Here you can see that we can cast the numerator to the integer underlying type using a few different
styles of casting.
Only then can we use the value of a scoped enumeration type variable.
Now that we've seen what stoked enumerations are and how to use them, let's head on over to the ID
and see a few examples.
Okay.
So I'm in the i.t.
I'm in the enumerations workspace and in the scoped in nums project.
I'm going to close this workspace for you just to give myself a little bit more room here.
All right, so what we've got here is this to test examples that show you different ways to use scoped
enumerations.
So I've got them both set up and I've already built and run the program.
You can see it over here on the right.
And what I want to do is go over those examples so you can see exactly what's happening.
This first test is very, very similar to the same first test that we did in the scoped enumerations
project.
Here we've got our grocery item enumeration.
I notice that this is now a scoped enumeration because we're using enum class here.
I've got milk, bread, apple and orange, just like before.
The difference is I've assigned some initialized values to these enumerators and this could represent
the item code or really just about anything you can think of that could identify these the values for
these enumerators.
So in this case we are explicitly defining those values and the compiler will not generate zero, one,
two and three.
We're going to do exactly what we did before with maybe a little bit of a change.
Okay.
So look at some of the examples we've written here.
Here we're overloading the insertion operator again, the stream insertion operator, just like we did
before.
The only real difference is that is that now in order to use those enumerators, we must scope them.
We have to use the enumeration class name grocery item right before the enumerator value.
Otherwise it's not going to compile.
So that's one of the benefits of using scoped enumerators.
The other thing we're going to do is in this case, I'm grabbing the underlying type of that grocery
item value right here.
So all I'm doing is displaying that.
So when we display these values, we're going to display you can see up here, Apple 132, milk 350
and so forth.
What I do is I'm going to switch off the grocery item passed into me.
I'm going to put whatever string is appropriate on the output stream, and then I'm just going to display
the value right after that colon.
And the value again is one of these guys.
Those are the ones I'm grabbing from the underlying type.
I've got the Boolean function again exactly like it was in the previous example with scoped.
Except since it's a scoped enumeration, we need to qualify those enumerators.
So in this case, whatever grocery item is passed into this function, if it's one of these four, then
we know it's valid.
Otherwise we'll return false because it's invalid.
And then the display grocery list function that I wrote is exactly the same.
Again, the whole point of this was to create an scoped version and a scoped version so you can see
the difference.
So in this case, we've got that stupid vector of grocery items just like we did before, and we're
going to loop through that using the range based for loop right here on line 64 and just display them.
So that's pretty simple.
We'll keep track of what's valid and invalid.
I went over this in more detail in the scoped video so you can check it out there.
And then what we'll do is we will create some items and add them to the shopping list, right?
So that's what test one is doing.
And you could see what I'm doing right here.
And let me grab my pen right here.
We're simply displaying test one and that's done right up here.
There's the output.
I'm creating my shopping list and I'm adding not Apple, I'm adding grocery item Apple.
We've got to scope it.
So I'm adding apple milk and orange and I'm creating a helicopter integer with 1000 and I'm going to
push that guy back in.
Now, I can do that here because I'm casting.
So remember, be very careful with casting.
Make sure your cast makes sense.
In this case, it doesn't make sense really, because I don't have a white item with a 1000 code.
Notice that if I try to add helicopter here, I'll get a compiler error because this is an INT and it's
doing a little bit of checking for me.
The shopping list is expecting a grocery item, not an int here.
I'm telling you, hey, I know what I'm doing, so do what I tell you to do.
And that's not always a good thing.
I'm doing the 350 item, which is the milk, so it's going to add that again.
And when I'm done, I display the list and here's the list that displays.
I get apple, I get milk, I get orange, I get that invalid item, which is this one right here.
On my 93, I get milk again because I'm adding the item code 350 and we've got five total items, four
valid, one invalid.
So there you go.
That's very, very similar to the example that I did in the scoped video.
So now let's check this example.
Test two for test two, I created a class just to show you how we can use these scoped enumerators or
really scoped enumerators inside a class.
So here's an example.
I've got a class called Player and this would model a player in a video game or something like that
where they've got a mode which is an attack, a defense or an idle right, their current mode in the
game direction that they're facing north, south, east or west, and they've got a name.
So you could see those attributes of the class right here down at the bottom right here between 129
to 131.
So again, you can see that the player has a name, it has a mode of attack or defense, whatever their
their current mode is and the direction that they're facing in the game.
So those are the attributes that if I scroll back up, we've got a constructor here just like we've
done before, nothing new.
We've got our getters and our setters for the name, the mode and the direction.
So let's write a couple of utility functions.
The first one gets me the player mode.
So if I pass in not a player, but if I pass in a player mode.
It's going to return attack defense or idol as a string.
That'll be a really handy function that I can call whenever I need to display that, for example.
I also have a function that's going to give me the player direction.
It works exactly the same way.
It expects a player's direction, not a player, just the player's direction.
And it returns the stud string here, and it's going to return north, south, east or west, depending
on that enumeration.
Since it's a scoped enumeration, I need to qualify it.
And then finally I've overloaded the stream insertion operator and this is where those functions that
I just wrote will be called so drain insertion.
Operator If we're passing in a player object by reference, in this case by const references as we're
supposed to, this is simply going to display player name and then it's going to get the player's name,
then player mode and it's going to get the player mode, that function we wrote and it's going to pass
in the player's mode rather than the entire player.
We don't need to pass in the entire player to display the mode and we're going to display player direction
followed by get player direction and port direction.
We'll put an inline at the end and we'll return OS and now we'll be able to use that.
So in test two, you can see the test.
It's really, really simple here.
I'm creating three player objects right here on lines 190 to 192.
The first one is P one.
It's cloud strife.
Its current player mode is attack and its current direction is facing north.
And you can see how we're using those enumerators right here.
Also, you can see that the way the scope works, right?
It's not just mode attack, it's player mode attack, because that enumeration is defined in the player
class.
So it's defined publicly within the scope of the player class.
So we need to create so we need to provide all the information that the compiler needs to do this.
But look at the readability again, that's very readable, right?
Player one.
Player two is Tifa Lockhart in defense mode facing West.
This is so much easier than having magic numbers again in there and the compiler is helping us out.
It's checking for errors.
So once I did find those three player objects, I can display them really simply by just putting those
player objects right on the output stream here with the insertion operator.
And this is what we're going to get as the output right there.
Play your name, cloud strife, attack mode north, just like we did here, Tifa Lockhart, Defense
West and Sephiroth Idle South.
And you can see that display right there.
Now, I didn't implement test three, which is the rocket launch example.
I'll leave that to you as an exercise.
That way you can translate it from an scoped to a scoped enumeration and play around with that and have
some fun.
So that covers enumerations, as you can see at their core, they're very, very simple.
Using them involves a lot because we can use a lot of advanced features in C++.
We can use them with all kinds of control structures, we can use them inside classes, we can overload
operators, we can get underlying types.
We can do all kinds of really cool stuff.
The big benefits readability.
Also, let the compiler help you find mistakes.