C++26: range support for std:optional
34 comments
·October 10, 2025steveklabnik
tialaramex
The fun thing is that the original design (N3527 over a decade ago) for std::optional specifically says it's not the container with exactly zero or one items, whereas I believe Rust's Option never made this claim and so recognising this isn't a U-turn.
jmrm
I really like how some good structures used by other languages, specially Rust and Zig, have been added to the newer C++ standard.
The Result, Optional, and Variant are really sweet for day-to-day use of the language, and those in-process standard libraries of SIMD operations, BLAS mathematical functions, and the the execution library looks really cool, specially as standard.
I would like C++ would be a little more "batteries included" in some ways, like having a basic standard for signals, networking (just handling sockets would be a huge thing), and some basic system calls.
fair_enough
"I would like C++ would be a little more "batteries included" in some ways, like having a basic standard for signals, networking (just handling sockets would be a huge thing), and some basic system calls."
Besides basic handling of TCP sockets and the Unix-style "Ctrl-c" keyboard interrupt, none of the stuff you're asking for is portable across different platforms. I'm not saying it's a bad idea, just that there is no one single universal standard for what an OS should do and what knobs and levers it should expose, or at least one that everybody follows.
Linux has non-trivial deviations from the POSIX spec, and even FreeBSD and OpenBSD have deviations. POSIX has its own compliance test suite that it runs to award certification of compliance, but it's not open source and it you need to pay a fee for it.
All of that however, is a drop in the bucket compared to making an API that exposes all the knobs and levers you want in a way that behaves exactly the same on Windows which barely has any architectural resemblance to UNIX. For exmaple, NTFS is case-insensitive by default and has nothing resembling the UNIX style of file permissions. Or more importantly, signals do not exist on Windows; something resembling signals for keyboard interrupts exists, but stuff like SIGHUP and SIGBUS does not. I'm talking the kind of known caveats that come with using a POSIX-compatibility layer on Windows, e.g. Cygwin.
I think if I get much deeper than that I'm just being pedantic, but even Python code behaves differently on Windows than it does on all the POSIX-like OSes out there.
jcelerier
> I really like how some good structures used by other languages, specially Rust and Zig, have been added to the newer C++ standard. The Result, Optional, and Variant are really sweet for day-to-day use of the language, and those in-process standard libraries of SIMD operations, BLAS mathematical functions, and the the execution library looks really cool, specially as standard.
for Optional and Variant they both were basically standardized versions of boost.optional & boost.variant, which exist since 2003 and 2002 respectively. Most of the time you can just change boost:: to std:: and it works exactly the same ; for many years software I develop could switch from one to another with a simple #ifdef due to platforms not supporting std::optional entirely (older macOS versions, pre 10.14 IIRC)
forrestthewoods
std::variant is an abomination that should never be used by anyone ever. Everything about it is sooooo bad.
spacechild1
That's a bit hyperbolic. Sure, it's not exactly ergonomic, but that doesn't mean I can't use it. One thing that bugs me is that there is still no overload helper in the standard:
// helper type for the visitor
template<class... Ts>
struct overloads : Ts... { using Ts::operator()...; };
Of course, having true pattern matching would be much nicer. At least there's a proposal: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p26...nrclark
out of curiosity, can you elaborate on that a bit? I've shipped std::variant in a few designs and it's been OK, but maybe I didn't use it deeply enough to see issues.
tialaramex
C++ is a proud New Jersey language. So the priority has been and continues to be ease of implementation. If it's tricky to make it work right, don't sweat it - the users (in this case that's us programmers) will put up with it not working right.
std::variant has valueless_by_exception - the C++ language wasn't able to ensure that your variant of a Dog or a Cat is always, in fact, a Dog or a Cat, in some cases it's neither so... here's valueless_by_exception instead, sorry.
coffeeaddict1
It doesn't have proper pattern matching (you have to use a visitor class or the weird overload trick), and it sucks for compile times. It should have been a language construct rather than adding it to std, but the committee is often too scared to touch the language because they are stubbornly unwilling to break backwards compatibility.
nmeofthestate
Herb Sutter calls this kind of thing "spelling things generically" meaning that the same generic code will compile against different types. In this case the same for loop code will compile against a type that may hold zero or one items and a type that may contain 0->n items. Maybe this pattern could be extended to shared_ptr for example.
jasonthorsness
It's sort of like a cleaner approach to a C# SelectMany where you return either an empty array or a single-item array?
C++ keeps getting more stuff but it's clear a lot of thought goes into these additions. I think a "living" language has proven better over time (C# evolved heavily and borrowed from other languages, C++ does as well, maybe lately concepts from functional and Rust?) Go might be the big exception that evolves super-slowly.
jb1991
This is absolutely magnificent. Game-changing improvement, really shows how amazing the C++ committees are working.
spacechild1
It's hard to tell if you are being serious or joking. And I say that as a C++ aficionado :)
Simran-B
I'm sensing a pinch of sarcasm
DSingularity
The for syntax over optional seems unnatural. I get that optional is like a list of 0 or 1 owns. Is there a fault in my reading that makes the syntax clunky?
lanyard-textile
Putting it through a for loop demonstrates that it is iterable, imo.
template <typename T>
void foo(const T& container) {
for (auto value : container) {
// process value
}
}
foo(std::vector<int>())
foo(std::optional<int>())
mkehrt
What seems weird here? Iterating or mapping over Options is pretty normal in functional languages.
null
lo_zamoyski
Well, you can map over options/maybes. For-loop style iteration over an option does seem a little strange IMO, first, because syntactically it looks like overkill, and second (and more importantly), because map evals to a value, while `for` does not.
But I suppose in C++, given the centrality of looping constructs, this is what you would do to accommodate option.
steveklabnik
With syntax questions, it can help to get very concrete. There's a few different bits of syntax in this post, what is it you find clunky?
kstrauser
Not the person you were replying to, but
for (auto l : logger) {
l.log(data);
}
bent my brain for a moment. `logger` is a list of loggers to send data do? Oh, no, it's either 0 or 1 loggers.Rust's `if let` syntax maps much more closely to how I think about it. I guess if this becomes idiomatic C++ then it'd start looking perfectly normal to everyone, but it still seems odd. For instance, I don't think I could ever bring myself to write Python like this, even if it worked:
def do_something(data, logger=None):
for l in logger:
l.log(data)
steveklabnik
That makes sense! What's funny is, I went to go write the equivalent Rust code:
struct Logger;
impl Logger {
fn log(&self, _: &str) {}
}
fn do_something(data: &str, logger: Option<Logger>) {
for l in logger {
l.log(data)
}
}
fn main() {
let logger = Some(Logger);
let data = "foo";
do_something(data, logger);
}
and rustc actually warns about this: warning: for loop over an `Option`. This is more readably written as an `if let` statement
--> src/main.rs:8:14
|
8 | for l in logger {
| ^^^^^^
|
= note: `#[warn(for_loops_over_fallibles)]` on by default
help: to check pattern in a loop use `while let`
|
8 - for l in logger {
8 + while let Some(l) = logger {
|
help: consider using `if let` to clear intent
|
8 - for l in logger {
8 + if let Some(l) = logger {
|
I was actually going to say "I've never seen folks in Rust write out for loops this way, even though you can" and I suspect this lint has something to do with that!I think this is most useful in generic contexts, I agree that I would not write this exact code this way because it does feel weird, if you know you have an Option, if let is the way to go. But if you're writing code over generic iterators, it can be nice that it exists.
loeg
Seems like a good thing broadly. I haven't personally found a use for ranges, ever, but this seems consistent.
nurumaik
Maybe somewhere in C++60 we will finally have proper monads
jeffbee
The example program seems confusing and pointless. Who uses the bell for debug logging? I found it distracted from the central point of the article as I stared at this bizarre program.
Strilanc
> the people writing the standard are not exactly known for adding features “just because”
Ah yes, C++, the discerning language.
Iterating over optional does seem syntactically convenient. My main question would be if it guarantees no overhead. For example, is there an additional conditional branch due to the iterator hiding the statically know fact that there's at most one iteration? I don't use C++ for its beauty, I use it for speed.
loeg
> Ah yes, C++, the discerning language.
C++, the language that refused to add `contains()` to maps until, you know, C++20!
nzeid
This gave me a good chuckle. For those wondering, `count` was the go-to and I can tolerate an argument that `contains` is a negligible improvement.
nikanj
They didn't stop there, C++23 brought contains() to strings too! You can do some crazy stuff nowadays like say if(username.contains("hello"))
Absolutely incredible, definitely worth the wait
tialaramex
Note that despite this work in C++ you can't flip the script to if ("hello".contains(username)) unlike Rust
Rust will also let you: "FOO".contains(char::is_uppercase)
That's showing off three clever tricks Rust has that C++ does not, but one day it will just be compile time evaluated as true and that's one place C++ is richer today. Many other things in Rust can be but Rust's traits aren't today allowed to be compile time evaluated, so the fact that we could determine at compile time whether there's an uppercase letter in FOO isn't enough yet. One day.
criemen
Great to see they learned from Java, which initially made the mistake of not supporting the streams interface for their Optional type at first [1]. It was infuriating.
[1] https://stackoverflow.com/questions/22725537/using-java-8s-o...
shadowgovt
I do enjoy seeing C++ pick up these features from other languages.
... it's unfortunate that the feature then ends up in C++ syntax, which is increasingly divorced from the way it's used today, the kind of thing that nobody would have written from scratch if they were starting day-1 with these ideas in play. I look forward to having the features, but not to having to read things like `const auto flt = [&](int i) -> std::optional<int>` or `for (auto i : std::views::iota(1, 10) | std::views::transform(flt))`.
When we added the equivalent to Rust, it was a bit controversial. But the idea of "an optional type is a list with zero or one items in it" is much more normal in functional places, and so a lot of people like it quite a bit too.