Type Aliases

Type-Safe Enum Pattern with Aliases

How can I use type aliases to implement a type-safe enum pattern in C++?

Illustration representing computer hardware

Implementing a type-safe enum pattern using type aliases in C++ is an interesting technique that can provide additional type safety and functionality compared to traditional enums.

This pattern is particularly useful when you want to create strongly-typed enumerations with custom behavior. Let's explore how to implement this pattern step by step.

Basic Implementation

Here's a basic implementation of a type-safe enum pattern using type aliases:

#include <iostream>

class Color {
 public:
  class ColorType {
    friend class Color;
    ColorType() = default;

   public:
    bool operator==(const ColorType&) const
      = default;
  };

  static const ColorType Red;
  static const ColorType Green;
  static const ColorType Blue;

  using Type = const ColorType&;

 private:
  Type m_value;

 public:
  constexpr Color(Type value)
    : m_value(value) {}
  constexpr operator Type() const {
    return m_value;
  }
};

const Color::ColorType Color::Red{};
const Color::ColorType Color::Green{};
const Color::ColorType Color::Blue{};

int main() {
  Color c = Color::Red;

  if (c == Color::Red) {
    std::cout << "The color is red!\n";
  }
}
The color is red!

In this implementation:

  1. We define a Color class with a nested ColorType class.
  2. We use using Type = const ColorType&; to create an alias for our enum type.
  3. We define static constant members for each enum value.
  4. The Color class wraps the Type and provides implicit conversion to Type.

Adding Functionality

We can extend this pattern to add functionality to our enum:

#include <iostream>
#include <string>

class Color {
  class ColorType {
    friend class Color;
    std::string m_name;
    explicit ColorType(const std::string& name)
      : m_name(name) {}

   public:
    bool operator==(const ColorType&) const
      = default;
    const std::string& name() const {
      return m_name;
    }
  };

 public:
  static const ColorType Red;
  static const ColorType Green;
  static const ColorType Blue;

  using Type = const ColorType&;

 private:
  Type m_value;

 public:
  constexpr Color(Type value) : m_value(value) {}
  constexpr operator Type() const {
    return m_value;
  }
  const std::string& name() const {
    return m_value.name();
  }
};

const Color::ColorType Color::Red{"Red"};
const Color::ColorType Color::Green{"Green"};
const Color::ColorType Color::Blue{"Blue"};

int main() {
  Color c = Color::Green;

  std::cout << "The color is " << c.name();
}
The color is Green

This version adds a name() method to our enum values, allowing us to get a string representation of each color.

Type Safety Benefits

This pattern provides strong type safety. For example:

#include <iostream>

class Color {
  class ColorType {
    friend class Color;
    ColorType() = default;

   public:
    bool operator==(const ColorType&) const
      = default;
  };

 public:
  static const ColorType Red;
  static const ColorType Green;
  static const ColorType Blue;

  using Type = const ColorType&;

 private:
  Type m_value;

 public:
  constexpr Color(Type value)
    : m_value(value) {}
  constexpr operator Type() const {
    return m_value;
  }
};

const Color::ColorType Color::Red{};
const Color::ColorType Color::Green{};
const Color::ColorType Color::Blue{};

class Shape {
 public:
  class ShapeType {
    friend class Shape;
    ShapeType() = default;

   public:
    bool operator==(const ShapeType&) const
      = default;
  };

  static const ShapeType Circle;
  static const ShapeType Square;

  using Type = const ShapeType&;

 private:
  Type m_value;

 public:
  constexpr Shape(Type value)
    : m_value(value) {}
  constexpr operator Type() const {
    return m_value;
  }
};

const Shape::ShapeType Shape::Circle{};
const Shape::ShapeType Shape::Square{};

int main() {
  Color c = Color::Red;
  Shape s = Shape::Circle;

  // This will not compile
  if (c == s) {}
  
  // This will not compile either
  Color wrongColor = Shape::Circle;
  
  // But this is fine
  if (c == Color::Red) {
    std::cout << "The color is red!";
  }
}
error: binary '==': 'Color' does not define this operator
error: initializing: cannot convert from 'const Shape::ShapeType' to 'Color'

In this example, we can't compare a Color to a Shape, or assign a Shape value to a Color variable. This level of type safety is not possible with traditional C++ enums.

Conclusion

Using type aliases to implement a type-safe enum pattern in C++ provides several benefits:

  1. Strong type safety: You can't accidentally use one enum type where another is expected.
  2. Extensibility: You can add methods and properties to your enum values.
  3. Encapsulation: The implementation details are hidden within the class.
  4. Flexibility: You can control the underlying type and behavior of your enum.

This pattern is particularly useful in large codebases where type safety is crucial, or when you need enums with custom behavior.

However, it does come with some overhead in terms of code complexity and potential runtime cost compared to traditional enums.

As with any pattern, consider the trade-offs for your specific use case before implementing it.

This Question is from the Lesson:

Type Aliases

Learn how to use type aliases, using statements, and typedef to simplify or rename complex C++ types.

Answers to questions are automatically generated and may not have been reviewed.

This Question is from the Lesson:

Type Aliases

Learn how to use type aliases, using statements, and typedef to simplify or rename complex C++ types.

A computer programmer
Part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, unlimited access

This course includes:

  • 125 Lessons
  • 550+ Code Samples
  • 96% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Free, Unlimited Access

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Screenshot from Warhammer: Total War
Screenshot from Tomb Raider
Screenshot from Jedi: Fallen Order
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved