
2018-01-28 1:02 GMT-03:00 Emil Dotchevski via Boost
Where is the parallel control flow in return parse(read_data(open_file()))?
The hidden throw. int a = 4; int x() { a = y(); return z(a); } Any C programmer will think this function is as clear as it can be. Add a C++ programmer in the game and he'll start to think about possible exception interactions and maintain a useless mind state in his head which solves no problems. Lots of languages do fine without exceptions. And Rust, which is very similar to C++ (only pay for what you use, systems programming language, RAII...) also does fine without exceptions. You do have less complication. Go write a container and then you don't know whether functions will throw. Now you have to maintain two lines of control flow in your head. It won't be fun at all. Some would count the fact that with e.g. Outcome you can specify what kind
of errors can be returned as an advantage, but that is similar to statically enforced exception specifications. Sutter explained why that is a bad idea back in 2007: https://herbsutter.com/2007/01 /24/questions-about- exception-specifications/ https://herbsutter.com/2007/01/24/questions-about-exception-specifications/ .
This post will teach what is the proper way to "catch everything" in response to "how can I be sure I'm catching all exceptions?". However, one of the points is to not introduce new error types ever. Why would a user who is calling parse_int handle errors other than "invalid data" and "overflow error"? What should the developer put in the catch-all statement? Sometimes, a catch-all statement won't make sense. The problem here — really — is a brittle architecture in large code bases to handle error cases (which should happen only occasionally and most likely won't be tested as much as the success case). Add a new type failure that isn't expected by the callers and you break everything. If the user doesn't have access to the sources of an updated library, he won't even be able to test if his calls need to be updated. Now, add this to the fact that exception specification is a bad-practice/not-really-used in C++ and you may agree that this architecture is brittle. If I refactor large code bases in Rust projects, I'll never face similar problems. Everything will be compile time errors without any special setup that only advanced users could pay. This post you link here will also explain why exception specifications isn't a good idea. The argument is twofold: - “no one really knows how to design them”. - They are broken. What does this have to do with Outcome? This is not the topic being discussed here. The conclusion to this question is irrelevant here (a.k.a. ignoratio elenchi). Though this reminds me: in C++, exceptions are the only way constructors
may report an error, and this is very deliberate, integral part of RAII. This guarantees that you can't use an object that failed to initialize, which is the reason why member functions are free to assume, rather than check, that all invariants of the class have been established.
C++ didn't have Expected or Outcome's result back then. This is why exceptions are the only way to report an error from destructors. But a constructor is just a function which will instantiate an object of the given class. The only "special" requirement for such function is ACL to access all class' members. You have this idiom in C++ (private constructors). Whether you choose to design your objects using exceptions or Boost.Outcome, your functions /can/ rely on invariants of the classes. So, nothing special about exceptions here except for legacy code. Conclusion: exceptions are only an integral part of RAII in C++. Rust does have RAII and it doesn't need exceptions. If the objects you are instantiating use Boost.Outcome instead exceptions, then there is no reason why you need exceptions to orchestrate the "object fully constructed" scenario. Moves will never throw. You can just construct the object parts (potentially returning early in case of failure) and them moving everything into a structure of the proper class. Maybe I'm simplifying too much. Tell me if that is the case. Thus, exception handling is an integral part of the C++ object
encapsulation model. Choose to not use exceptions and the result is that like in C, each function must check whether the object was initialized, and return some error code to indicate that condition. You're replacing a bullet-proof automatically enforced error checking system with a manual one, prone to errors; worse, we're talking about error handling code, which by its very nature is difficult to test.
Given your previous argument was attacked, you can no longer follow/derive this argument. Therefore, it is not this much manual system as you imply. There are many languages which lack C++ exception handling, it doesn't mean
that their approach is better. It is common for programmers coming from a different background, forced by reality to have to use C++, to complain what a horrible language it is for lacking this or that feature. :)
Okay. I'd much rather use return parse(read_data(open_file())) without having to
assume anything.
Write `return open_file(path).and_then(read_data).and_then(parse);` then. It'll compile and work. Much simpler than to design a container which needs to take consideration the hidden control flow of exceptions everywhere. How much effort did we lose into this already? There was one of the review comments here about Niall getting the swap exception specification wrong, for instance. Is it every day and every week? And errors popping without we noticing? What is the argument to have such brittle error system (exactly the thing which should be the most robust)? -- Vinícius dos Santos Oliveira https://vinipsmaker.github.io/