1. Problem It Solves
Several fallible steps otherwise require repeated checks and early returns. C++23 monadic operations connect them while preserving the first error and skipping later success work. The goal is to make the relevant rule visible in code instead of relying on an assumption about what “C++23 support” means.
2. Prerequisites
Day 25:
optionalmonadic vocabulary.Day 26: success and error alternatives in
expected.
3. Core Idea
Imagine a railway switch. and_then continues on the success rail, transform changes the carried value, and or_else can inspect or replace the error rail. Read the syntax from left to right, identify the value, object, or range being transformed, and then check its resulting type, lifetime, or ownership. Standardization and implementation are separate: a C++23 mode may still lack one library component, so a feature-test macro is part of responsible portable use.
4. Minimal Syntax
read().and_then(parse).transform(normalize).or_else(report);5. How It Works
The sample builds the smallest expression or object needed for Monadic Operations and Error Pipelines with
std::expected.It chains two fallible integer steps when monadic
expectedsupport is present. The compiler and library apply the relevant rule before the program observes the result.The program prints or checks the final transformed value or a truthful library-support message, making the important behavior easy to verify.
6. Common Mistakes
Mismatched error types stop composition, and using
transformfor a function that already returnsexpectedproduces an unwanted nestedexpected.Enabling C++23 mode without checking the relevant feature macro may select code that the installed compiler or standard library does not implement yet.
7. When to Use It
Use it when the task involves linear validation or conversion pipelines whose steps share a coherent error model.
Avoid it when the task involves business logic with branching recovery that is clearer through explicit statements.
8. Simple Example
A request pipeline decodes a number, validates it, and converts it to a display string while preserving the first failure message. The downloadable program keeps the data fixed so the output can be compared without entering input.
Complete sample code
Source file
cpp23/27_expected_monadic_pipelines/main.cpp
#include <expected>
#include <iostream>
#include <string>
#include <version>
std::expected<int, std::string> begin_value() {
return 3;
}
std::expected<int, std::string> double_positive(int value) {
if (value <= 0)
return std::unexpected(std::string{"not positive"});
return value * 2;
}
int main() {
#if defined(__cpp_lib_expected) && __cpp_lib_expected >= 202211L
auto result = begin_value()
.and_then(double_positive)
.transform([](int value) { return value + 1; })
.or_else([](const std::string& error)
-> std::expected<int, std::string> {
return std::unexpected(error);
});
std::cout << *result << '\n';
#else
std::cout << "expected monadic operations unavailable\n";
#endif
}
9. Key Takeaways
Separate the language rule from compiler and library availability.
Keep lifetime, ownership, and deduced types visible when they affect correctness.
Prefer the smallest syntax that communicates the intent.
Guard facilities that are not yet uniformly implemented.
10. Self-Check Questions
Easy — What is the smallest syntax in Section 4, and what main job does it perform?
Medium — Read the sample program: which value, type, or branch is observed, and why?
Hard — Why does
transform(f)yieldexpected<expected<U, E>, E>whenfreturnsexpected<U, E>, and which operation avoids that nesting?