CppCon 2018: Andrei Alexandrescu “Expect the expected”
Вставка
- Опубліковано 26 сер 2024
- CppCon.org
-
Presentation Slides, PDFs, Source Code and other presenter materials are available at: github.com/Cpp...
-
Writing code that is resilient upon errors has always been a pain point in all languages. Exceptions are the politically correct means to signal errors in C++, but many applications still resort to error codes for reasons related to ease of understanding, ease of handling errors locally, and efficiency of generated code.
This talk shows how a variety of theoretical and practical artifacts can be combined together to address error codes and exceptions in one wholesome, simple package. The generic type expected<T> can be used for both local (error-code-style) and centralized (exception-style) manners, drawing from the strengths of each.
-
Andrei Alexandrescu, The D Language Foundation
Vice President
Andrei Alexandrescu is a researcher, software engineer, and author. He wrote three best-selling books on programming (Modern C++ Design, C++ Coding Standards, and The D Programming Language) and numerous articles and papers on wide-ranging topics from programming to language design to Machine Learning to Natural Language Processing. Andrei holds a PhD in Computer Science from the University of Washington and a BSc in Electrical Engineering from University "Politehnica" Bucharest. He is the Vice President of the D Language Foundation.
-
Videos Filmed & Edited by Bash Films: www.BashFilms.com
*-----*
Register Now For CppCon 2022: cppcon.org/reg...
*-----*
I love the way he presents! Very conversational, and natural, while still hitting all the salient details and not just outright repeating whats on the slides. And funny, throwing in jokes - not nervous at all. He also anticipates our possible thinking stumbling blocks and even addresses the natural tangential side-thoughts. Totally comprehensive. And from a non-native speaker of English, it's all the more impressive. What a legend! (much better than Titus - total opposite in fact) - almost on Bjarne's level. Big fan of his contributions too. He really sees the big picture and doesn't get distracted.
Thank you very much!
3 years later and this still is in proposal.
Love the idea, hate the semantics
"I heard you not saying", LOL.
I am going to use this quote from now on.
Andrei is a phenomenal speaker!
He's absolutely right about how expected should behave, down to the last detail. I really hope the standards committee makes it and does it the way he recommends, right down to not having operator *.
I've been thinking through how to do error handling for a hypothetical C++ wrapper to the POSIX/Linux api. I came to the exact same conclusion for how it should be done.
Compilers are good enough at flow analysis to emit diagnostics if you never check things like `expected`, or even if you ignore any exected.
I think a solution to expected would be to annotate functions returning that as nodiscard, that way you would at least get a compiler warning if you just ignore the return type
edit: ok exactly that question was asked right at the end
Around ~ minute 4 it was clear that this will sound like C++'s version of Rust's "Result".
I've just read about Rust error handling and the ? operator is pretty neat (nothing like the ternary in C++ for those wondering). With std::expected it would be like calling .value() just after receiving one std::expected from a function, i.e. auto a = openFile("..").value(); What I liked in Rust is that, if opening the file failed, the calling function would just propagate the error in it's own returned Result, when in C++ you enter the alternative path of exception propagation.. It would be more verbose to have the Rust-like behavior in C++, i.e. auto a = openFile(".."); if (!a) return unexpected(a.error()); Too much typing =p
@@seidenada526 If you are curious, I recommend also checking "failure" on "crates.io", it's error chaining made easy :)
Dropbox did say that they observed that their Rust code is not only free of memory management issues but also has a lot less bugs in general - my guess is that because you have to handle the error case in Rust and you just slap "?" everywhere you want to propagate the error and handle it where you want to recover for example, this way it's very simple to handle all error cases(failed to open file? failed to parse the JSON from it? failed to read an int value from that JSON? No problem, just slap "?" and handle all of it :) ).
Big possible change at 33:55 wrt. exceptions - and at 34:00 I even did the clapping myself :)
To create exceptions and use them for handling errors, *without* throwing the exceptions immediately is IMHO a really great idea! Such a simple solution: a function should return a real value or exception object. Simple, powerful, beautiful. Go, JF!
Update: We finally have expected in C++23!
22:50 does anyone have a link to the function by Herb Sutter mentioned?
At 50:00: For those who want the returned and unchecked error to throw (in the destructor of expected): yes I implemented such a solution and 'it works'. But you generally don't need it if you mark your functions nodiscard and check your warnings!
I finally decided that Andrei's voice reminds me of Triumph the insult comic dog. "This code is really great... for me to poop on!"
34:45 Is there any difference between calling the copy constructor with placement new vs. using a member initialization list, i.e. : yay(rhs) ?
If you write your application using expected with exception handling deactivated, then .value() is surly also undefined behavior?
Wouldn't returning `expected` show less intention than returning `optional`?
Since in the end both return types say 'this function might return an error or nothing'.
My guess is, that `expected` would be preferred only for homogeneity among other return types.
> Wouldn't returning `expected` show less intention than returning `optional`?
I'd say that depends on whether the actual name of `error_type` indicates clearly that it represents an error. If the error type is `int`, that is not the case. Although that depends a lot on what customs and conventions will get adopted in the wild.
You probably can and want to define a "expected::flatMap(f: function from A to expected) returning expected", which will let you sequentially combine computations which return `expected` (not calling `f` if the step that tried to produce an `A` failed). Treating `void` specially would make `flatMap` less powerful.
I guess I'm making the homogeneity-is-good argument-though more for programming capability reasons that easy readability reasons.
where is monadic bind operator?
1. Great and entertaining talk.
2. There's a ')' missing in slide 30, I think.
Yeap, I noticed it almost instantly and then I thought it was put after assert by mistake (not the case though).
it is mimicking the NAN convention in floating point computation.
37:55 I'm not 100% sold on the constructor-based thing. Personally I would have a private constructor with named constructors std::expected::ofValue and std::expected::ofError. Though I see that's a bit more verbose.
edit: I see at 47:50 something like that exists, so you can use either expected() or unexpected()
@Ziggi Mon the copy and unexpected constructors are more specific, and the default constructor cannot be instantiated anyway if T is not default constructible (it is instantiated only when someone tries to use it, not when the class is instantiated). You only need enable_if to control the overload set of valid instantiations
I don't understand why they discarded the approach with storing an std::exception_ptr instead of some template argument E inside the Expected. If I remember correctly, that's what Andrei initially proposed a few years ago when presenting the idea of Expected. That approach looked much cleaner (no need to specify two template arguments) and more flexible (you could store any kinds of exceptions in the Expected, even if the function "throws" multiple unrelated exception types).
Is there an explanation why this decision was made now?
boost::result defaults the second type parameter to std::error_code. If you want, you can declare a type alias that does that for std::expected
MaceUA I think because of error codes (aka ints).
So if anyone familiar with IO[A, E] or even Either[A, B] types in FP -- this is essentially the same with a lot of C++ noise. Furthermore, the proposed implementation doesn't compose well (52:12)
Coming back to C++ after not using it in over a decade and working predominantly in Java / Scala, I am amazed at how utterly crappy std::optional is.
@D X I started catching up with all the changes made in C++11, 14, and 17 just maybe six months ago, and I'm really loving it, which surprised me, because C++98 is what originally put me off of programming for a long time (until I learned Python). I love template metaprogramming in particular (even though I have a huge ton to learn on that subject) and playing around with constexpr to see what I can do with it.
I agree that STL types compose / inherit very well in most cases. std::optional seems like the exception rather than the rule, but that may be because the STL is completely iterator based, and giving optional iterators would work (and I'm surprised they haven't done it - it would make it fit much better into the STL) but admittedly would look weird.
I think that many C++ programmers fail to understand how much optional types have changed programming with pointers / references because optional in C++ is so limited. It really seems to offer little benefit over using nullptr, unlike, say, Java or Scala, where you can just easily chain together transformations and filters over the optional types so nicely and really see the benefit of having a monad of optional.
Wasn't boost::optional more advanced than stl::optional? I should look into that as a point of curiosity.
Hmmm... it would be a fun challenge to make an optional type that works with iterators. Another project for me to think about.
@@vorpal22 Don't forget optional, as an std thing, is _less then an year old!_ Monadic interface is proposed (there is a paper) and probably optional of reference will come as well. People like optional a lot, so it will improve for sure.
@@YourCRTube How long has it been in boost? From the boost optional page, it looks like at least four years, and possibly more than 10. I really hope that there are some improvements in mind for C++20. (I'm intrigued and terrified by C++20... it seems like there will be many new things there and I'm just catching up to 14 and 17.)
I'm glad to hear that optional is well liked. I honestly have no idea... I'm just starting to get into the C++ community. I felt very intimated by the fact that I hadn't programmed in C++ for so long, but I'm starting to feel more comfortable and all the C++ programmers I've chatted with online have been very patient and helpful.
I'd really like to go to some C++ conferences in the next year... CppCon and C++ by Sea both are calling to me. Do you have any recommendations?
@@YourCRTube BTW... thanks for pointing me towards the monadic interface paper! I'm going to sit down and read it over. I'm definitely intrigued.
At 45:59 - is desctructor of temporary `E t` called on scope exit?
yes, it is.
47:56 I wonder if the missing parenthesis was unexpected...
Oi... thanks!
Can someone explain, why did they use placement new instead of simple initializing in constructors?
Because this is the way to construct an object without allocating memory - union is C-thing and it does not know about constructors, but it has the memory already allocated.
@@YourCRTube Still don't really understand why expected(const T& rhs) : yay(rhs) {} is worse than placement new, in both cases yay will be constructed once (and I suppose memory for yay will be allocated only once as well)
I believe the primary reason it is written this way is so that union is not mistaken for class - placement new is the "union style" of starting the lifetime of an object. From the standard: [Note: In general, one must use explicit destructor calls and placement new-expression to change the active
member of a union. -end note]
Non-trivial data members of unions have to be constructed that way (and you have to call the destructor on your own as well!). Construction initialization for unions is only allowed for trivial types.
@@damianjarek1974 I did some research and found this reply on stackoverflow stackoverflow.com/questions/33058717/do-unrestricted-unions-require-placement-new-and-a-constructor-definition
basically it says that in this particular case both ways are correct.
Slide 28: it might be better to use std::conjunction to short-circuit the expression inside enable_if.
Also, another part of homework for implementers: make this swap() function conditionally noexcept, where it can be. That could help some other generic code to be more efficient.
I really expected an expected to be inside the other expected but Andrei failed my expectations!
yet another way to handle errors in cpp?
The guy is funny
found local choreography normal (cause they do java instead of c++) LOL