On Fri, Aug 5, 2016 at 6:57 PM, Josh Juran
On Aug 5, 2016, at 2:13 PM, Lorenzo Caminiti
wrote: I would like to discuss contracts and exception specifications (e.g., noexcept) specifically with respect to what Boost.Contract does.
I’m not following C++ standards development closely, but perhaps my comments may be of some use.
However, noexcept can be considered part of the function contract (namely, the contract that says the function shall not throw)
For example consider a noexcept function fclose() that is called from
I’m going to use POSIX’s close() as an example in this discussion.
a destructor without a try-catch statement (correctly so because fclose is declared noexcept). If fclose() is now allowed to throw when its preconditions fail, that will cause the destructor ~x() to throw as well?!
If passed -1 as an argument, close() will set errno = EBADF and return -1. This is documented behavior. A function that throws an exception when passed -1 (or under any other circumstance) is not the close() function from POSIX and should be given a different name (if it’s declared in the global namespace, at least).
POSIX close() has a "wide contract" (i.e., no preconditions). For my example, I made up my own fclose() that has a narrow contract instead (i.e., it has one or more preconditions). The fclose() I use in my example is not POSIX close(). Don't worry about POSIX close(), just assume there's some sort of fclose() defined as I stated it for the sake of the example I am illustrating.
void fclose(file& f) noexcept [[requires: f.is_open()]]
I don’t see why this should be treated differently than
void fclose(file& f) noexcept { if ( ! f.is_open() ) throw failed_precondition();
For example, this strategy will not work for class invariants failing at destructor entry because it will make the destructs throw (which is not a good idea in C++, and in fact destructors are implicitly declared noexcept in C++11): class x { bool invariant() const { ... } ~x() { if(!invariant()) throw failed_invariants(); ... This topic has been discussed in great length for C++... quoting N4160 (but also N1962, P0380, many other contract proposal for C++, previous emails on this list, etc.): ``What can a broken contract handler do? The most reasonable default answer appears to be std::terminate, which means "release critical resources and abort". One may wish to override the default in order to do special logging. We believe that it is not a good idea to throw from a broken contract handler. First, throwing an exception is often an action taken inside a function,in a situation where it cannot satisfy the postcondition, to make sure that class invariants are preserved. In other words, if you catch an exception or are in the middle of stack unwinding, you can safely assume that all objects' invariants are satisfied (and you can safely call destructors that may rely on invariants). If it were possible to throw from the handlers, his expectation would be violated. ...'' The above fclose() contract actually expands to something that calls a "precondition failure handler" functor: void fclose(file& f) noexcept { if (!f.is_open()) preconfition_failure_handler(from_function); ... Where by default the handler terminates: precondition_failure_handler = [] (from) { std::terminate(); }; But programmers can redefine it to throw (as you suggested above, but beware of what N4160 points out plus on how to program a throwing entry_invariant_failure_handler that shall not throw when from == from_destructor): precondition_failure_handler = [] (from) { throw failed_precondition(); }; Or to log and exit: precondition_failure_handler = [] (from) { some-logging-code; exit(-1); }; Or to take any other action programmers wish to take on precondition failure. Note: Boost.Contract does something a bit more complex than the above (using set/get functions to not expose the handler functor directly to the users, try-catch statements to call the handlers, etc.). These details are omitted here for simplicity.
The purpose of noexcept, as I understand it, is to ensure that a called function will under no circumstances throw an exception, sparing the caller the need to wrap it in try/catch. Adding a loophole that allows compiler-provided glue to throw, even when the function itself strictly speaking doesn’t, invalidates this guarantee and undermines the utility of noexcept, in my opinion.
Yes, I agree. That is essentially the point I was trying to make and what Boost.Contract does--no loopholes.
If the compiler can statically prove that the precondition is always satisfied, fine. But if not, then I’d prefer that the above code not compile unless `noexcept` is removed.
While desirable, this is not possible in practice. 1. First, it'd be great if the preconditions could be checked statically, but in general some times the preconditions will be satisfied and some other times they will not and the compiler will simply not know. Even if contracts were added to the language (as per N1962, P0380, etc.), static analysis tools will be able to statically check preconditions only some times, and not all the times. 2. Second, note that the compiler (and most likely static analysis tools) will not know if the action to take on contract failure is to throw or not because they will only see a call to precondition_failure_handler(from_function) and not `throw failed_precondition()`. What such failure handler call does is configured at run-time by the programmers so probably not always deductible at compile-time by the compiler or static analysis tools. Thanks, --Lorenzo