How can I use an enumeration as a template parameter?
Categories:
Using Enumerations as Template Parameters in C++
Explore the powerful technique of leveraging C++ enumerations as non-type template parameters, enhancing type safety and compile-time control in generic programming.
C++ templates are a cornerstone of generic programming, allowing us to write flexible and reusable code. While common template parameters include types and compile-time constants, enumerations (enums) offer a unique and powerful way to specialize templates. This article delves into how to effectively use enumerations as non-type template parameters, providing strong type safety and enabling compile-time selection of behavior based on distinct enumerated values.
Understanding Non-Type Template Parameters
Before diving into enums, let's briefly revisit non-type template parameters. These parameters allow you to pass values (not types) to a template at compile time. Common examples include integral types like int
or size_t
, or pointers/references to objects or functions. The key is that the value must be a compile-time constant. This characteristic makes enumerations an ideal candidate.
template <int N>
struct MyArray {
int data[N];
// ... other methods
};
MyArray<10> arr;
A simple example using an integer as a non-type template parameter to define array size.
Enums as Template Parameters: The Basics
Using an enumeration as a non-type template parameter allows you to select different template specializations or behaviors based on a specific enum value. This provides a type-safe alternative to passing raw integers, making your code more readable and less prone to errors. Both enum class
(scoped enums) and traditional unscoped enums can be used, with enum class
generally preferred for its stronger type safety.
enum class ProcessingMode {
FAST,
NORMAL,
SECURE
};
template <ProcessingMode Mode>
class DataProcessor {
public:
void process(const std::string& data) {
if constexpr (Mode == ProcessingMode::FAST) {
// Fast processing logic
std::cout << "Processing in FAST mode.\n";
} else if constexpr (Mode == ProcessingMode::NORMAL) {
// Normal processing logic
std::cout << "Processing in NORMAL mode.\n";
} else if constexpr (Mode == ProcessingMode::SECURE) {
// Secure processing logic
std::cout << "Processing in SECURE mode.\n";
}
}
};
// Usage
DataProcessor<ProcessingMode::FAST> fastProcessor;
fastProcessor.process("some data");
DataProcessor<ProcessingMode::SECURE> secureProcessor;
secureProcessor.process("confidential data");
Demonstrates using enum class
to specialize a DataProcessor
.
enum class
as a template parameter, remember to fully qualify the enum value (e.g., ProcessingMode::FAST
). For traditional enum
, direct use of the enumerator (e.g., FAST
) is sufficient, but less type-safe.Advanced Usage: Compile-Time Dispatch and Policy-Based Design
The true power of enums as template parameters shines in scenarios requiring compile-time dispatch or implementing policy-based design. By varying the enum value, you can instantiate entirely different implementations or inject distinct behaviors into a class, all resolved at compile time. This eliminates runtime overhead and allows the compiler to optimize specific code paths.
Compile-time dispatch based on enum values.
enum class LogLevel {
ERROR,
WARNING,
INFO,
DEBUG
};
template <LogLevel Level>
struct LoggerPolicy {
static void log(const std::string& message) {
if constexpr (Level == LogLevel::ERROR) {
std::cerr << "[ERROR]: " << message << "\n";
} else if constexpr (Level == LogLevel::WARNING) {
std::cout << "[WARNING]: " << message << "\n";
} else if constexpr (Level == LogLevel::INFO) {
std::cout << "[INFO]: " << message << "\n";
} else if constexpr (Level == LogLevel::DEBUG) {
std::cout << "[DEBUG]: " << message << "\n";
}
}
};
template <LogLevel Level>
class Application {
public:
void run() {
LoggerPolicy<Level>::log("Application started.");
// ... application logic ...
LoggerPolicy<Level>::log("Application finished.");
}
};
// Usage
Application<LogLevel::INFO> app_info;
app_info.run();
Application<LogLevel::DEBUG> app_debug;
app_debug.run();
Implementing a logging policy using an enum template parameter.
Considerations and Best Practices
When using enums as template parameters, keep the following in mind:
- Readability: Enums inherently improve code readability compared to raw integral constants.
- Type Safety:
enum class
provides stronger type safety, preventing accidental conversions. - Compile-Time Optimization: The compiler can often optimize branches (
if constexpr
) based on the known enum value at compile time, leading to efficient code. - Limited Scope: Non-type template parameters must be compile-time constants. This means you cannot pass a runtime variable enum value to a template.
- Underlying Type: For traditional
enum
, the underlying type is implementation-defined. Forenum class
, it defaults toint
but can be explicitly specified (e.g.,enum class MyEnum : short { ... };
). Ensure the underlying type is suitable if you need to perform arithmetic or conversions.
Using enumerations as non-type template parameters is a sophisticated C++ technique that offers significant benefits in terms of type safety, readability, and compile-time performance. By embracing this pattern, you can design more robust, flexible, and optimized generic code.