Overview

9 Algebraic data types and pattern matching

This chapter motivates designing programs so that illegal or contradictory states are impossible to represent. It introduces algebraic data types as a practical way to model state and behavior in C++, using a simple word-counting example to show how replacing scattered flags with explicit states removes ambiguity, prevents runtime mistakes, and lets RAII automatically manage resources tied to particular states.

Product types (structs/tuples) combine values, while sum types represent alternatives. The chapter contrasts open sum types via inheritance with closed, type-safe sums via std::variant, highlighting that variants store values (not pointers), avoid dynamic allocation, and encode all possibilities in the type. It then presents std::optional as a specialized sum for “maybe” values and builds an expected utility for returning either a result or an error, yielding clearer APIs, safer lifetimes, and easier error propagation.

For domain modeling, the Tennis kata shows how to split the game into precise states (normal scoring, forty scoring, deuce, advantage) so invalid combinations cannot occur. To work ergonomically with these ADTs, the chapter advocates pattern matching: in standard C++ via std::visit plus overloaded callables for type-directed dispatch, and, for richer patterns, with libraries like Mach7. The result is exhaustive, compiler-checked handling of all cases and code that is safer, clearer, and easier to evolve.

Implementing sum types with inheritance is simple. You can create multiple classes that inherit a common state_t class, and the current state is denoted by a pointer pointing to an instance of one of these classes.
c09_01.png
Variants can be used to define proper sum types. You get a value type that can contain a value of any of the summed types in its own memory space. The variant instance will be at least the size of the biggest type you’re summing, regardless of the value it currently holds.
c09_02.png
When counting the number of words on a web page, you have three main states. Before you have the web page, you can’t do anything. When you get the web page, you can start counting. While counting, you need to store the current count and the handler to the web page. When the counting is finished, you no longer need the handle to the web page; you can just store the final count.
c09_03.png

Summary

  • Implementing program state through algebraic data types requires some thinking and produces longer code, but it allows you to minimize the number of states your program can be in and removes the possibility of having invalid states.
  • Inheritance, dynamic dispatch, and the visitor pattern are often used in OO programming languages to implement sum types. The main problem with using inheritance to implement sum types is that it incurs runtime performance penalties.
  • If you know exactly which types you want to sum, variants are a better choice for implementing sum types than inheritance. The main downside of std::variant is that it incurs a lot of boilerplate because of the std::visit function.
  • Unlike exceptions—which should denote, as their name indicates, exceptional situations a program can find itself in—optional values and the expected class are perfect for explicitly stating the possibility of failures. They also travel across multiple threads and multiple processes more easily.
  • The idea of having a type that contains either a value or an error has been popular in the functional programming world for a long time. This concept became popular in the C++ community after Andrei Alexandrescu gave a talk called “Systematic Error Handling in C++” at a 2012 C++ and Beyond conference (http://mng.bz/q5XF), where he presented his version of the expected type. That version of expected was similar to ours, but it supported only std::exception_ptr as the error type.

FAQ

What are algebraic data types, and what do “sum” and “product” mean in C++?
  • Product type: combines values of multiple types together. In C++, classes/structs, std::pair, and std::tuple are product types.
  • Sum type: holds a value of one of several alternatives (mutually exclusive). In C++, enums, std::variant, and std::optional are sum types.
  • Rule of thumb: use product types to say “and,” sum types to say “either/or.”
How do algebraic data types help remove invalid states?
  • They model only valid combinations of data, making illegal states unrepresentable.
  • Example: Replace separate flags like started/finished with a sum type of states: init | running | finished. You can’t, for instance, be both running and finished.
  • Resources live only in the states that need them (e.g., a socket exists only in running), so lifetimes are correct by construction.
How can I implement a sum type with inheritance versus std::variant?
  • Inheritance (open sum): - Base class (state_t) + derived classes (init_t, running_t, finished_t). - Store a pointer to base, use virtuals/tags/casts to dispatch.
  • std::variant (closed sum): - std::variant<init_t, running_t, finished_t> stores the value directly (no heap by default).
When should I prefer std::variant over inheritance (and vice versa)?
  • Prefer std::variant when: - The set of alternatives is fixed. - You want value semantics, no virtuals, and minimal boilerplate. - You want no dynamic allocation by default.
  • Prefer inheritance when: - You need extensibility (plugins/new alternatives later). - Polymorphic behavior across an open set is required.
  • Note: std::any offers type-erased storage but is less efficient/expressive than std::variant for closed sums.
How do I safely access or check the active alternative in std::variant?
  • std::get<T>(v): returns T& or throws std::bad_variant_access if not active.
  • std::get_if<T>(&v): returns T* or nullptr if not active.
  • v.index(): returns the index of the active alternative.
  • Tip: Use std::visit with an overloaded visitor to avoid manual checking.
What is std::optional, and why is it better than using pointers for “maybe” values?
  • std::optional<T> is a sum type: “a T or no value.”
  • Advantages over raw pointers: - Clear intent (no ownership confusion). - Value semantics and RAII (no lifetime pitfalls). - Expressive API: has_value(), operator*, operator->.
  • Use pointers when denoting identity, polymorphism, or shared ownership; use optional for absence/presence.
How can I model computations that may fail and still carry error details?
  • Use a sum type like expected<T, E>: either a value T or an error E.
  • Benefits over optional: - Includes rich error info (code/message/exception_ptr).
  • Benefits over exceptions: - Makes failure explicit in the type and control flow.
  • Implementation can be a tagged union or a thin wrapper around std::variant<T, E> with a nicer API.
How do I model a domain (like tennis scoring) with ADTs to avoid illegal states?
  • Decompose the domain into disjoint, meaningful states, each with only the data it needs: - normal_scoring (0/15/30 vs 0/15/30), - forty_scoring (leader at 40 vs other up to 30), - deuce, - advantage (who has it), - optionally game_over.
  • Use std::variant over these types to make 40–40 or mixed states unrepresentable by mistake.
How do I get pattern-matching-like behavior with std::variant in C++?
  • Use std::visit with an “overloaded” visitor:
    template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
    template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
  • Then:
    std::visit(overloaded{
      [](const normal_scoring&){ /* ... */ },
      [](const forty_scoring&){ /* ... */ },
      [](const deuce&){ /* ... */ },
      [](const advantage&){ /* ... */ }
    }, state);
  • This emulates pattern matching by type with concise, type-safe dispatch.
What extra capabilities does the Mach7 library provide for pattern matching?
  • Richer patterns than std::visit: - Match on structure (deconstruction), specific values, and guards. - Write multiple cases that discriminate both by type and by field values.
  • Trade-offs: - Non-standard library, different (more verbose) syntax. - Useful when you need advanced matches beyond simple type-based dispatch.
What practical tips should I keep in mind when using ADTs and pattern matching in C++?
  • Keep public APIs clear: avoid exposing raw tuples/pairs; prefer named structs.
  • Localize variant access with visitors to keep code exhaustive and readable.
  • Let states own only what they need; transitions naturally release resources.
  • Choose closed sums (variant) by default; use inheritance only when openness is required.
  • Use optional for absence; use expected (or similar) when you also need error details.

pro $24.99 per month

  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose one free eBook per month to keep
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime

lite $19.99 per month

  • access to all Manning books, including MEAPs!

team

5, 10 or 20 seats+ for your team - learn more


choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Functional Programming in C++ ebook for free
choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Functional Programming in C++ ebook for free
choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Functional Programming in C++ ebook for free