CodeNebula
C++23stlexpert

std::expected Monadic Operations (C++23 Addition)

C++23 · Completing Modern · Compiler flag: -std=c++23

Explanation

Adds `and_then`, `transform`, and `or_else` to `std::expected`, enabling functional-style chaining of operations that may fail.

Real-World Scenario

Chain file read, parse, and validate steps where each may return an error.

Quick Snippet

snippet.cpp
auto result = read_file("config.json")
    .and_then(parse_json)
    .transform(extract_settings)
    .or_else(use_defaults);

Full Example

expected_monadic.cpp
// CodeNebula - C++23 Example: std::expected Monadic Operations
// Compile: g++ -std=c++23 -Wall -Wextra -o example expected_monadic.cpp
// Note: Requires GCC 13+ / Clang 17+

#include <expected>
#include <iostream>
#include <string>
#include <cmath>

// std::expected<T, E> (C++23) holds either a value T or an error E.
// It supports monadic operations:
//   and_then(f) - flatmap: if has value, call f(value) -> expected
//   transform(f) - map: if has value, wrap f(value) in expected
//   or_else(f)   - if has error, call f(error) -> expected
//   transform_error(f) - map the error type

enum class MathError { DivByZero, Negative, Overflow };

std::string to_string(MathError e) {
    switch (e) {
        case MathError::DivByZero: return "division by zero";
        case MathError::Negative:  return "negative input";
        case MathError::Overflow:  return "overflow";
    }
    return "unknown";
}

using Result = std::expected<double, MathError>;

Result safe_divide(double a, double b) {
    if (b == 0.0) return std::unexpected(MathError::DivByZero);
    return a / b;
}

Result safe_sqrt(double x) {
    if (x < 0.0) return std::unexpected(MathError::Negative);
    return std::sqrt(x);
}

Result safe_reciprocal(double x) {
    return safe_divide(1.0, x);
}

int main() {
    std::cout << "=== std::expected monadic operations ===\n\n";

    // --- Basic usage ---
    auto r1 = safe_divide(10.0, 3.0);
    auto r2 = safe_divide(10.0, 0.0);
    std::cout << "10/3 = " << (r1 ? std::to_string(*r1) : to_string(r1.error())) << "\n";
    std::cout << "10/0 = " << (r2 ? std::to_string(*r2) : to_string(r2.error())) << "\n\n";

    // --- and_then: chain failable operations ---
    std::cout << "--- and_then ---\n";
    auto chain1 = safe_divide(100.0, 4.0).and_then(safe_sqrt);
    std::cout << "sqrt(100/4) = " << chain1.value() << "\n";

    auto chain2 = safe_divide(100.0, 0.0).and_then(safe_sqrt);
    std::cout << "sqrt(100/0) = " << to_string(chain2.error()) << "\n";

    auto chain3 = safe_divide(-16.0, 1.0).and_then(safe_sqrt);
    std::cout << "sqrt(-16/1) = " << to_string(chain3.error()) << "\n\n";

    // --- transform: apply function to value ---
    std::cout << "--- transform ---\n";
    auto doubled = safe_divide(10.0, 4.0)
        .transform([](double v) { return v * 2.0; });
    std::cout << "(10/4) * 2 = " << doubled.value() << "\n";

    auto fail_transform = safe_divide(1.0, 0.0)
        .transform([](double v) { return v * 2.0; });
    std::cout << "(1/0) * 2 = " << to_string(fail_transform.error()) << "\n\n";

    // --- or_else: recover from errors ---
    std::cout << "--- or_else ---\n";
    auto recovered = safe_divide(1.0, 0.0)
        .or_else([](MathError e) -> Result {
            std::cout << "  Recovering from: " << to_string(e) << "\n";
            return 0.0;  // Default value on error
        });
    std::cout << "Recovered value: " << recovered.value() << "\n\n";

    // --- Full chain ---
    std::cout << "--- Full chain: divide -> sqrt -> reciprocal ---\n";
    auto full = safe_divide(100.0, 4.0)
        .and_then(safe_sqrt)
        .and_then(safe_reciprocal)
        .transform([](double v) { return std::round(v * 1000) / 1000; });
    std::cout << "1/sqrt(100/4) = " << full.value() << "\n";

    return 0;
}

// Expected output:
// === std::expected monadic operations ===
//
// 10/3 = 3.333333
// 10/0 = division by zero
//
// --- and_then ---
// sqrt(100/4) = 5
// sqrt(100/0) = division by zero
// sqrt(-16/1) = negative input
//
// --- transform ---
// (10/4) * 2 = 5
// (1/0) * 2 = division by zero
//
// --- or_else ---
//   Recovering from: division by zero
// Recovered value: 0
//
// --- Full chain: divide -> sqrt -> reciprocal ---
// 1/sqrt(100/4) = 0.2
Run command
g++ -std=c++23 -Wall -Wextra -o example expected_monadic.cpp