using
statements and typedef
to simplify or rename complex C++ types.At this point, we may have noticed our types are getting more and more verbose.
This trend is going to continue, particularly once we start using template classes.
We’ll cover template classes in more detail later in this chapter. For now, let's introduce a basic example of one from the standard library - the std::pair
.
The pair
data type is available by including <utility>
. It lets us store two objects in a single variable. We specify the types we will be using within the <
and >
syntax:
#include <utility>
std::pair<int, bool> MyPair;
We can access each value through the first
and second
members, respectively:
#include <utility>
std::pair<int, bool> MyPair { 42, true };
MyPair.first // 42;
MyPair.second // true;
This is all we need to know about std::pair
for now, but a full overview is available here:
Let's see a more complicated example, which will show why type aliases can be useful.
We might have a consumable item in our inventory, alongside an integer quantity. We could store that as a std::pair<const ConsumableItem&, int>
:
#include <utility>
class ConsumableItem {
public:
void Consume() const {}
};
void ConsumeInventoryItem(
std::pair<const ConsumableItem&, int> Item
) {
if (Item.second > 0) {
Item.first.Consume();
Item.second--;
}
}
int main() {
ConsumableItem HealthPotion;
int Quantity { 100 };
std::pair<const ConsumableItem&, int> Item {
HealthPotion, Quantity
};
ConsumeInventoryItem(Item);
}
In the above code, we’re using the following type:
std::pair<const ConsumableItem&, int>
This has three problems:
int
or Monster
represent, but it takes some digging and assumptions to work out that std::pair<const ConsumableItem&, int>
is supposed to be an inventory slot.Fortunately, we have a way to give our types more friendly names.
using
We can create an alias for a type using a using
statement, as shown below:
using InventorySlot =
std::pair<const ConsumableItem&, int>;
This has at least two benefits
We can use the type alias in place of the type, in any location where a type would be expected. From our previous example, it has simplified our function signature and our variable creation:
#include <utility>
class ConsumableItem {
public:
void Consume() const {
}
};
using InventorySlot =
std::pair<const ConsumableItem&, int>;
void ConsumeInventoryItem(InventorySlot Item) {
if (Item.second > 0) {
Item.first.Consume();
Item.second--;
}
}
int main() {
ConsumableItem HealthPotion;
int Quantity { 100 };
InventorySlot Item { HealthPotion, Quantity };
ConsumeInventoryItem(Item);
}
Through the alias, we can freely add qualifiers to the underlying type. This can include things like *
or &
to make it a pointer or a reference type, and const
to make it a constant:
ConsumableItem HealthPotion;
InventorySlot Potions { HealthPotion, 100 };
Potions.second++;
// Pointer to an inventory slot
InventorySlot* Pointer { &Potions };
Pointer->second++;
// A constant inventory slot
const InventorySlot ConstantSlot { Potions };
ConstantSlot.second++; // not allowed
Aliases are scoped in the same way as any of our other declarations.
This means aliases can have global scope, by being defined outside of any block:
using InventorySlot = std::pair<Item*, int>;
int main() {
InventorySlot Slot { &CheeseWheel, 100 }
}
Alternatively, aliases can be defined within a block, such as one created by a function, namespace (including an anonymous namespace) or an if
statement.
When this is done, the alias will only be available within that block, including any nested child blocks.
In the following example, line 7 will now throw an error, as the alias is only available within the block created as part of MyFunction
void MyFunction() {
using InventorySlot = std::pair<Item, int>;
InventorySlot Slot { BreadRoll, 50 }
}
int main() {
InventorySlot Slot { CheeseWheel, 100 }
}
decltype
Occasionally, it will be easier (or necessary) to ask the compiler to figure out what type an expression returns. We can do this using the decltype
function.
This returns the type of the expression, which we can use anywhere in our code where a type would be expected.
Here are some examples:
#include <utility>
int main(){
std::pair<int, int> SomePair;
decltype(SomePair) AnotherPair;
using PairType = decltype(SomePair);
PairType AThirdPair;
}
typedef
C++ also offers another way to accomplish type aliasing - typedef
.
The concept is very similar, although with typedef
, the type comes first, followed by the alias:
typedef std::pair<const Item&, int> InventorySlot;
The support of typedef
is mostly for historic reasons, but it still crops up a lot in existing code so it's worth knowing about.
New code should use the using
approach, as described previously.
Comprehensive course covering advanced concepts, and how to use them on large-scale projects.