6 ms·
C++26: range support for std:optional
- criemen 1y agoGreat 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-optional-with-streamflatmap https://stackoverflow.com/questions/22725537/using-java-8s-o...
- jb1991 1y agoThis is absolutely magnificent. Game-changing improvement, really shows how amazing the C++ committees are working.
- Simran-B 1y agoI'm sensing a pinch of sarcasm
- spacechild1 1y agoIt's hard to tell if you are being serious or joking. And I say that as a C++ aficionado :)
- Maxatar 1y agoI think the being an aficionado is clouding your judgement, because it's pretty obvious what the intent is.
- spacechild1 1y agoThat was tongue-in-cheek. Some people really are that enthusiastic about every minor C++ feature. Just watch certain cppcon talks.
- nikbackm 1y agoNo it’s not.
- steveklabnik 1y agoWhen 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.
- tialaramex 1y agoThe 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.
- CCs 1y agoScala has it like this since day one. Tried to convince D to adopt it, but was unsuccessful. Glad that C++ eventually got there…
- masklinn 1y agoIn most respect it's also "day 1" in rust, since it was added in 0.7 (https://github.com/alexcrichton/rust/commit/4f2f545ac2ce19034b138006e7ac76e502b3188b https://github.com/alexcrichton/rust/commit/4f2f545ac2ce1903...)
- anon-3988 1y agoNot so fun for me. I wasted a considerable amount of time debugging an issue like this because i had returned option of vec. Granted i have learnt better and should probably create an enum that explicitly describe what all the different states represent.
- Strilanc 1y ago> 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 1y ago> Ah yes, C++, the discerning language. C++, the language that refused to add `contains()` to maps until, you know, C++20!
- nikanj 1y agoThey 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 1y agoNote 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.
- flykespice 1y agoI mean you could make that work in C++ too, with ref qualifier method overloads: namespace std { class string { bool contains(const string& other) & { //Check if 'this' is in 'other' } bool contains(const string& other) && { //check if 'other' is in 'this' } }; } using namespace std::string_literals; int main() { string foo("foo bar foo"); foo.contains("bar"); //returns true "bar"s.contains("foo bar foo"); //returns true } But I hope not because this 'flipping the script' behavior just makes the code flow more difficult to reasonate about.
- DSingularity 1y agoThe 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?
- steveklabnik 1y agoWith 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 1y agoNot 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 1y agoThat 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 1y agoSeems like a good thing broadly. I haven't personally found a use for ranges, ever, but this seems consistent.
- mpyne 1y agoI've been using ranges fairly often but it's only for stuff like this: auto it = std::ranges::find(ctx.users, req_user_id, &User::user_id); If ctx.users is a vector<Users>, it will return an iterator pointing to the first matching User, by matching a field in User to a variable. It's just syntax sugar for std::find and a simple lambda but it's been really nice syntax sugar.
- nmeofthestate 1y agoHerb 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.
- jmrm 1y agoI 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.
- jcelerier 1y ago> 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)
- dgfitz 1y agoCorrect, they have been around for a lot longer than rust.
- jmrm 1y agoI knew some changes (like STL containers) came from Boost, but I didn't know those also came from there, and specially since such a long time! That means I need to look more Boost documentation :)
- gpderetta 1y agoOften the std flavored implementation is inferior of the boost one. Support for optional references has only be added to the draft standard recently, while bossy has had it since forever.
- jasonthorsness 1y agoIt'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.
- nurumaik 1y agoMaybe somewhere in C++60 we will finally have proper monads
- jeffbee 1y agoThe 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.
- shadowgovt 1y agoI 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))`.
- chris_wot 1y agoyou can pipe from a for statement now?!?
- aw1621107 1y agoThe pipe is entirely inside the for loop in the example.
- chris_wot 1y agoOh! so it is. Ta.
- shadowgovt 1y agoNot that it's easy to tell, given what C++ syntax has metastasized into.
- chris_wot 1y agoHmmm… I don’t mind all that much.
- Razengan 1y agoI don't know which I want to see happen during my lifetime more: Aliens making public contact or C++ being laid to rest.