Katkat

Design a C++ Variant with Safe Object Lifetimes

BalyasnySoftware Engineer · Onsite

Last updated

This problem tests your understanding of manual memory management, template metaprogramming, and the type safety requirements necessary to implement a discriminated union (variant) in C++.

The Task

Implement a simplified, type-safe SafeVariant class in C++ that can hold one of several possible types. Unlike std::variant, your implementation must strictly enforce object lifetimes and provide runtime safety.

Requirements

  1. Storage: Use alignas and std::aligned_storage (or std::byte array) to reserve sufficient space for the largest provided type.
  2. Type Tracking: Maintain an internal state to track which index is currently active. If the variant is empty, it should throw a custom exception upon access.
  3. Lifetime Management: Explicitly call destructors for the held object when the variant is assigned a new type or destroyed.
  4. Interface:
    • template class SafeVariant
    • template void set(T&& val): Constructs an object of type T in the storage.
    • template T& get(): Returns a reference to the held object if the type matches; throw an exception otherwise.
    • ~SafeVariant(): Ensures the active object is properly destroyed.

Constraints

  • You may assume Args are movable/copyable types.
  • The size of the SafeVariant should be max(sizeof(Args...)) + sizeof(size_t) (for the index).

Example

SafeVariant v;
v.set(42);
int val = v.get(); // Returns 42
v.set("hello"); // Properly destroys 42, constructs string

Edge Cases

  • Attempting to get the wrong type.
  • Exception safety during the construction of a new type (ensure the old type is preserved if the new one fails to construct).

Hints

Hint 1

Use a variadic template pack to determine the maximum size and alignment of the storage buffer using `std::max` and `std::max_align_t`.

Hint 2

Use `std::index_sequence` and a helper function with a switch statement or recursive template to route calls to the correct destructor based on the active index.

Hint 3

To handle exception safety, construct the new object in a temporary buffer before destroying the existing object, ensuring the variant remains in a valid state if construction throws.

Follow-up questions

  • How would you extend this design to support types that are not default-constructible?
  • What changes are required to make the `get` method `constexpr` compatible?
  • How does your implementation handle memory alignment issues if the user provides a type with an alignment requirement larger than `std::max_align_t`?