using
Keywordusing
keyword in C++, focusing on namespaces, enums, and type aliasingWhen dealing with excessively verbose code, C++ provides a few options for us to reduce the noise. Typically, these options have the using
syntax. We’ll explore the four main uses for it in this lesson:
using namespace
StatementsStarting from the very first lesson, we've had a line of code in our files that we've left unexplained thus far:
using namespace std;
Predictably, this line is related to namespaces.
The effect of this using
declaration was an instruction that asks the compiler: "if an identifier we use is not found, try searching for it in the std
namespace".
This using
statement has been necessary every time we used cout
and string
. This is because the standard library functionality, including these identifiers, is in the std
namespace:
#include <iostream>
int main() {
std::string MyString{"Hello"};
std::cout << MyString;
}
By adding a using namespace std
declaration, we have the option of removing the std::
prefix:
#include <iostream>
using namespace std;
int main() {
string MyString{"Hello"};
cout << MyString;
}
We can use using namespace
declarations with any namespace - not just std
:
namespace Maths {
float Pi { 3.14f };
}
using namespace Maths;
using namespace std;
int main() {
// equivalent to std::cout << Maths::Pi
cout << Pi;
}
What statement can we add below to make our call to Square()
work?
namespace Utilities {
void Square() {};
};
// ?
Square();
using
Declarations to Specific IdentifiersRather than having a using
statement that covers the entire std
namespace, we can also restrict it just to specific identifiers.
For example, to use it with just std::cout
and std::string
, we could write this:
#include <iostream>
using std::cout, std::string;
int main() {
string MyString{"Hello World"};
cout << MyString;
}
Hello World
using namespace
Statements?Probably not. Namespaces exist to help us organize code - a using namespace
declaration effectively subverts that organization.
Remember too how #include
directives work - if we have a using namespace
declaration that is scoped to an entire file, that would also affect any file that includes it.
This can cause things to get out of hand quite quickly so, if using namespace
declarations are going to be adopted, be particularly cautious when using them on files that are likely to be included in other files.
The Unreal coding standard asks for using
declarations to never be used in the global scope. We talk about non-global using
statements later in this lesson:
Do not put using declarations in the global scope, even in a .cpp file. It's okay to put using declarations within another namespace, or within a function body.
And the Google C++ standard asks that using
declarations never be used, anywhere:
Do not use using-directives (e.g., using namespace foo
).
If we want to use using
statements, there are two ways we can get the benefits whilst reducing many of the drawbacks.
using std::string
.using
statement, which we cover in the next lessonusing
DeclarationsAll of the using
declarations we cover in this lesson follow the normal block scoping rules of other statements. A using
declaration outside of any block has a "global" scope and will apply to the entire file.
This can be problematic, especially if that file is then added to further files, via the #include
directive.
As an alternative to global using
declarations, we can place them inside blocks - usually function bodies or other namespaces. This will limit their effect to just the scope of that block.
#include <iostream>
void Hello() {
// This using statement's effects are
// limited to just this function block
using namespace std;
cout << "Hello ";
}
void World() {
// Error - there are no using
// statements in effect in this scope
cout << "World";
}
int main() {
Hello();
World();
}
error C2065: 'cout': undeclared identifier
using enum
Statements (C++20)In some files, we may want to use the same enum repeatedly, and the constant qualification of MyEnumName::
would make our code excessively verbose.
We can solve this problem in the same we we tacked the equivalent issue with namespaces - by adding a using
statement - specifically a using enum
statement:
This can take our code from something like this:
enum class Faction { Human, Elf, Undead };
Faction MyFaction { Faction::Human };
To this:
enum class Faction { Human, Elf, Undead };
using enum Faction;
Faction MyFaction { Human };
Note, using enum
is a relatively recent addition to the language, included as part of the C++20 spec. In projects that use older compilers, it will not be available.
What statement can we add below that would make our Character
declaration valid?
enum class Hostility { Friendly, Neutral, Hostile };
// ?
class Character {
Hostility Hostility { Friendly };
};
using
The final scenario where we want to demonstrate a using
statement is for type aliasing.
This allows us to create a pseudonym for a type we want to use in our code:
using integer = int;
int main() {
integer MyNumber{42};
}
There are two main uses for this.
The first use case is to change what types we use based on a preprocessor definition.
For example, perhaps we want the specific type of integer we’re using to be different between platforms.
Rather than scattering that logic throughout our code, we could keep it in one place.
Below, we’re using int64_t
and int32_t
, which work like the int
type we’ve been using so far. The only difference is that int64_t
and int32_t
are fixed-width integers.
That is, they specify exactly how many bits they use, rather than letting the platform decide:
#pragma once
#include <cstdint>
#ifdef PLATFORM_64BIT
using integer = std::int64_t;
#else
using integer = std::int32_t;
#endif
We then #include
this file in all our other files, and use our new integer
alias like any other type:
#include "types.h"
int main() {
integer MyNumber{42};
}
The second use case for type aliases with the using
statement is to give complex types shorter, or more meaningful names.
This is of limited use for now when we’re dealing with simple types like int
and string
but, in C++, types can get fairly complex.
In the future, we’ll see types like the one used by this Inventory
variable:
std::unordered_map<
Enums::ItemType, std::vector<Item&>
> Inventory;
Scattering a type like this through our function prototypes in a class declaration, for example, would get very messy. So, we can use a using
statement to create a simpler alias:
using Items = std::unordered_map<
Enums::ItemType, std::vector<Item&>>;
class Character {
public:
void SetInventory(Items Inventory) {
mInventory = Inventory;
}
Items GetInventory() { return mInventory; }
private:
Items mInventory;
};
typedef
There is another way to create type aliases, which uses the typedef
syntax:
typedef int integer;
int main() {
integer MyNumber;
}
The typedef
syntax was inherited from the C language but is still commonly used. We tend to stick with using
, as it is compatible with templates, an advanced C++ feature we’ll introduce in the next course.
This lesson delves into the use of using
directives, exploring their applications in reducing verbosity, managing namespaces, and simplifying code through type aliasing
Key Learnings:
using namespace
statement allows for shorter code by omitting the namespace prefix, particularly with the standard (std
) namespace.using
declarations can be confined to specific blocks like function bodies, reducing their global impact.using enum
statement simplifies enum usage by eliminating the need for enum class qualification.using
enables switching types at compile time and giving complex types more meaningful names.using namespace
statement is recommended, particularly in the global scope.In our next lesson, we will delve into the world of arrays using std::vector
. The key topics we’ll cover include:
std::vector
: Introducing std::vector
as an implementation of a dynamic array, and the difference between dynamic and static arrays.std::vector
: Learn how to declare std::vector
, initialize it with values, and understand its dynamic nature.std::vector
, including using the subscript operator.std::vector
: How to iterate through std::vector
using various methods, including range-based for loops.std::vector
.using
KeywordThis lesson introduces the using
keyword in C++, focusing on namespaces, enums, and type aliasing
Become a software engineer with C++. Starting from the basics, we guide you step by step along the way