The problem
Consider this program:
#include <iostream>
enum class DoorState {
Open,
Closed,
Locked
};
class Door {
private:
DoorState m_state {};
public:
Door(DoorState state)
: m_state { state }
{
}
DoorState state() const{
return m_state;
}
bool isLocked() const{
return m_state == DoorState::Locked;
}
};
int main(){
Door frontDoor { DoorState::Locked };
if (frontDoor.state() == DoorState::Locked) {
std::cout << "The door is locked\n";
}
}
Terminal output:
The door is locked
This works, but DoorState exists outside the Door class although it’s designed to be used with Door.
If only there was a way to embed Door inside the class, thereby making the relationship explicit.
Nested types
So far we’ve covered member variables and methods. Class types support a third type of member: nested types/member types
A nested type is a type declared inside another type.
Let’s merge DoorState into Door:
#include <iostream>
class Door {
public:
enum State {
Open,
Closed,
Locked
};
private:
State m_state {};
public:
Door(State state)
: m_state { state }
{
}
State state() const{
return m_state;
}
bool isLocked() const{
return m_state == Locked;
}
};
int main(){
Door frontDoor { Door::Locked };
if (frontDoor.state() == Door::Locked) {
std::cout << "The door is locked\n";
}
}
Terminal output:
The door is locked
Now, the class now has three kinds of members:
1. Data members → m_state
2. Member functions → state(), isLocked()
3. Member types → State
Observe that the nested types are defined at the top of your class type. This is mandatory.
This refactored version has a few changes:
- Members (
isLocked()) do not need to access the nested type (DoorState) via the scope resolution operator::. - The nested types must be fully defined before it can be used
- We access the enumeration as
Door::Lockedrather thanDoor::State::Locked - The
enum classis changed to anunscoped enum.It’s redundant to leave it as an enum class since now it’s scoped to the class.
Nested type aliases
A class can also define a type alias inside itself.
This is useful when the class wants to expose a meaningful type name.
#include <iostream>
#include <string>
#include <string_view>
class Employee {
public:
using Id = int;
private:
Id m_id {};
std::string m_name {};
public:
Employee(Id id, std::string_view name)
: m_id { id }
, m_name { name }
{
}
Id id() const{
return m_id;
}
const std::string& name() const{
return m_name;
}
};
int main(){
Employee employee { 42, "Ada" };
Employee::Id id { employee.id() };
std::cout << employee.name()
<< " has employee ID "
<< id << '\n';
}
Terminal output:
Ada has employee ID 42
The alias is declared inside the class: using Id = int;
Outside the class, it is accessed with the scope resolution operator: Employee::Id
An int could represent anything. Employee::Id says what the integer represents.
Standard-library examples
It is very common for classes in the C++ standard library to make use of nested typedefs. As of the time of writing, std::string defines ten nested typedefs
For example, containers commonly provide names such as:
value_type
size_type
iterator
const_iterator
reference
const_reference
A std::vector<int> has a value_type of int:
#include <iostream>
#include <vector>
int main(){
// Type alias
using Scores = std::vector<int>;
Scores::value_type score { 95 };
Scores::size_type count { 3 };
std::cout << "Score: " << score << '\n';
std::cout << "Count: " << count << '\n';
}
Terminal output:
Score: 95
Count: 3
Takeaway
Classes can contain a third type of member in addition to member variables and member functions – member types.
Use nested enums when the enum is intended to be used with a class.