Using Enum in Modern C++
In modern C++, the using enum declaration introduced in
C++20 provides an elegant mechanism to import scoped enumerators
directly into a local scope. While scoped enumerations
(enum class) introduced in C++11 eliminated namespace
pollution and implicit type conversions, they often introduced verbose
and repetitive syntax. The using enum feature preserves the
underlying strong typing of scoped enumerations while allowing
developers to drop repetitive type prefixes within specific code blocks,
improving readability without sacrificing safety.
The Problem with Scoped Enumerations
Prior to C++20, developers working with enum class were
required to fully qualify every enumerator with its enumeration
name:
enum class Status {
Pending,
Approved,
Rejected
};
void process(Status s) {
if (s == Status::Pending) {
// ...
}
}This explicit qualification prevented name collisions in global and
namespace scopes. However, inside control structures—most notably
switch statements—repeating the type prefix adds
unnecessary visual clutter:
switch (s) {
case Status::Pending: break;
case Status::Approved: break;
case Status::Rejected: break;
}How using enum
Imports Enumerators
The using enum declaration injects all enumerators of a
designated enumeration into the declarative region where the statement
appears. The syntax is straightforward:
using enum TypeName;When this statement is placed inside a local block (such as a
function body, a loop, or a switch block), the enumerators
become unqualified identifiers within that block only. Once execution
leaves that scope, the unqualified names are no longer visible,
preventing namespace pollution.
void handleStatus(Status s) {
switch (s) {
using enum Status; // Introduces Pending, Approved, Rejected into this switch block
case Pending:
// Handled without qualification
break;
case Approved:
// Handled without qualification
break;
case Rejected:
// Handled without qualification
break;
}
}Scope Boundaries and Lifetime
The visibility of imported enumerators strictly respects C++ scope resolution rules:
- Block Scope: Declaring
using enuminside curly braces ({}) confines the unqualified names exclusively to that block and any nested blocks. - Class Scope: Declaring
using enumwithin a class definition introduces the enumerators as members of that class, accessible to member functions without qualification and to outside code via the class name. - Namespace Scope: Placing the declaration at the namespace level makes the enumerators available throughout that namespace. While valid, this is generally discouraged in header files to prevent unexpected identifier collisions.
Importing Individual Enumerators
In addition to importing an entire enumeration with
using enum, modern C++ also permits importing individual
enumerators using standard using syntax:
void evaluate(Status s) {
using Status::Approved; // Only 'Approved' is introduced into local scope
if (s == Approved) {
// 'Approved' can be used without qualification
} else if (s == Status::Pending) {
// 'Pending' still requires qualification
}
}Safety and Collision Handling
The using enum declaration does not convert a scoped
enumeration into an unscoped one; the underlying type remains strongly
typed. The compiler will still disallow implicit conversions to integers
or comparisons between distinct enumeration types.
If an imported enumerator shares an identifier with an existing local variable or function within the same scope, the compiler issues a redeclaration error. If the conflicting identifier exists in an outer scope, the newly imported enumerator shadows the outer identifier within that local scope, adhering to standard C++ name lookup behavior.