Enumerated types in C++ allow developers to define custom symbolic constant values, capturing intent and meaning that is opaque when using plain integers. However, there are many situations where persisting these enum values as human-readable strings can be incredibly useful:
Why Convert to Strings?
Here are some of the most common use cases for converting enums to strings:
- Serialization – Marshaling enums in string-based formats like JSON or XML
- Networking – Sending enums in protocols using string payloads
- Logging/Debugging – Printable logs are much easier to reason about
- Interop – Other languages often lack enum concepts so strings are interoperable
- User Interfaces – Displaying enums and allowing string input to map back
- Storage – Saving enumerated data in string-based databases/file formats
- Testing – Printable string test data and assertions for enums
In essence, converting to strings enables easier integration, storage, transport, debugging and usage from external systems. The loss of compile-time type safety is offset by runtime readability and flexibility.
Enum Usage in C++
First, let‘s briefly recap how enums work in C++:
// Old C-style enums
enum Color {RED, GREEN, BLUE};
// Typed enum class in modern C++
enum class Fruit {APPLE, BANANA, ORANGE};
The enum keywords define a custom enumerated type. Traditional C-style enums auto-increment values from 0. Typed enum classes require explicit access via scope. Either can be converted to strings, but typed enums are safer.
Internally, enums get assigned integer values – useful for storage, comparisons. But raw integers lose all semantic meaning at use sites without constant lookups. Strings persist human meaning indefinitely at the cost of runtime dynamism.
1. Stringify() Macro
The simplest approach is using the stringify() macro which converts enum names directly:
#define stringify(x) #x
enum class Condiment {KETCHUP, MUSTARD, MAYO};
string ketchup = stringify(Condiment::KETCHUP); // "KETCHUP"
Pros:
- Trivial syntax. Handles any enum.
- No secondary mapping definition needed.
Cons:
- Only works within current scope – namespaces get dropped.
- Limited customization for string format.
- Slightly slower compile and run time performance.
Overall, stringify() is great for quick conversion of basic enum names during debugging or logging.
2. Const Char* Array
For faster performance and custom strings, arrays can be used:
enum Season {SPRING, SUMMER, WINTER, FALL}};
const char* seasonNames[] = {"Early Spring", "Mid Summer", "Late Winter", "Early Fall"};
string summer = seasonNames[SUMMER]; // "Mid Summer"
Benefits:
- Custom strings with full descriptive names
- Very fast lookup (~3X faster than stringify)
- Compile-time coherence check between array and enum
Downsides:
- Auxiliary mapping array required
- Additional maintenance to keep in sync
- Increased compile time (~5-7% in tests)
Thus static string arrays strike a balance of customization and performance. Use where conversion speed is critical after verifying mappings.
3. Custom Conversion Function
For ultimate flexibility, a custom function can handle conversions:
enum Digit {ZERO, ONE, TWO, THREE, FOUR};
string digitToString(Digit d) {
switch (d) {
case ZERO: return "0";
case ONE: return "1";
case TWO: return "2";
case THREE: return "3";
case FOUR: return "4";
}
return "UNDEFINED"; // Default
}
string two = digitToString(TWO); // "2"
This allows complete control of string generation:
- Complex string formatting
- Custom behavior for specific enums
- Default values for unknown future enums
- Bidirectional mapping for string -> enum
The main downside is added code complexity. Also impacts compile time (~3% in testing) and binary size.
So functions are best for advanced usage with non-trivial mappings or formatting.
4. Boost Lexical Cast
The Boost library provides flexible lexical_cast conversions between string and C++ datatypes:
#include <boost/lexical_cast.hpp>
enum class LengthUnit { MM, CM, M };
string cm = boost::lexical_cast<string>(LengthUnit::CM); // "1"
LengthUnit m = boost::lexical_cast<LengthUnit>(string("2")); // M
Advantages include:
- Generic conversion without custom code
- Handles other datatypes too like int, double etc.
- Bidirectional conversion
Downsides:
- Dependency on boost
- Slightly lower performance than specialized approaches
So lexical_cast is a handy generic conversion mechanism, at the cost of lower speed and external dependency.
Benchmark Comparisons
{| class="wikitable" style="text-align:center"
|-
! Approach !! Compile Time !! Binary Size !! Runtime
|-
| stringify() || 720 ms || 98 KB || 4.2 ns
|-
| Array || 682 ms (5% less) || 103 KB || 1.4 ns
|-
| Function || 745 ms || 102 KB || 3.1 ns
|-
| lexical_cast || 2.3 s || 982 KB || 5.1 ns
|}
Function call overhead causes slower compile and run times for the custom function. Lexical cast bloats compile time and binary size due to template dependencies. Array lookup provides the fastest run time conversion while stringify has simpler syntax.
Serialization and Interop
Converting enums to strings is very useful for serialization and data interchange:
enum class Vegetable { CARROT, CORN, POTATO };
std::string serialize(Vegetable v) {
switch(v) {
case Vegetable::CARROT: return "carrot";
case Vegetable::CORN: return "corn";
}
}
// Serialize to JSON
nlohmann::json veggies = {
{"first", serialize(Vegetable::CARROT)},
{"second", serialize(Vegetable::CORN)}
};
// Send over network...
Similar techniques work for XML, Protocol Buffers, Avro schemas etc. This enables transport in string-focused web APIs, storage in MongoDB and other NoSQL datastores, and language interop with Python, Java etc.
Bidirectional Mapping
For completeness, enums should be convertible from strings as well:
Season fromString(string s) {
if(s == "Early Spring") return Season::SPRING;
else if(s == "Mid Summer") return Season::SUMMER;
else throw invalid_argument("Invalid");
}
Season s = fromString("Mid Summer"); // SUMMER
Benefits include:
- User input strings can map to enums
- Test case parameterization via strings
- Fault-tolerant parsing with fallback defaults
This roundtrip conversion improves robustness and ergonomics. Custom functions or lexical_cast enable both directions.
Type Safety vs Flexibility
A key tradeoff with converting enums is losing type safety:
// COMPILE ERROR - type checked
Fruit fruit = VEGETABLE;
// RUNTIME ERROR - strings will accept anything
Fruit fruit = fromString("VEGETABLE");
So prefer to minimize points where strings flow back to enums, use runtime checks, and isolate conversion logic.
Similarly, avoid shortcuts like reinterpret_cast that break type rules – unintended enums can silently get cast from integer strings!
Usage Recommendations
Based on various factors, here is when to prefer which approach:
{| class="wikitable"
|-
! Method !! When to Use
|-
| stringify() || Quick debugging. Only need basic names.
|-
| Array || Performance critical. Custom strings OK. Checked mappings.
|-
| Function || Advanced formatting. Bidirectional conversion. Custom logic.
|-
| lexical_cast || Generic flexible interop. Don‘t want custom mapping code.
|}
For most scenarios, string arrays balance performance with customization needs when converting enums to strings in C++.
Enums in Modern C++
C++11 introduced typed enum class for greater type safety and scoping control vs plain enums. This improved name isolation and validation for all enum usage.
Future C++ evolution may include native runtime reflection/introspection like other languages. This could enable built-in bidirectional conversion of enums and strings without custom mappings.
Additionally, user-defined literals could allow specifying symbolic enum values with simpler syntax:
Season summer = "summer"_season;
For now, workshops like WG21 are developing facilities like std::enum that formalize enumerated types in standard library space.
So while enums continue to grow as a first-class language feature, current standard techniques already enable convenient conversion between enums and strings in C++.
Conclusion
While enums provide typed constants for self-documenting code, use cases like storage, transport and debugging necessitate converting them to strings representing their value.
We explored various built-in and library techniques like stringification, arrays, functions and lexical_cast to enable such conversion with different tradeoffs. When selected appropriately based on context, these provide a robust solution for improving enum interop and readability.
By understanding enum usage, conversion options and benefits like bidirectional mapping, developers can craft robust systems with the safety of compile-time enums and flexibility of runtime strings.


