Le 2023-11-09 14:41, Andrzej Krzemienski via Boost a écrit :
Hi Everyone, I would like to discuss one correctness aspect of Boost.COBALT. We have this list and the Slack channel, so I am not sure which place is better. Let me do it here.
In Boost.Cobalt (formerly Boost.Async) we have function cobalt::race (formerly select()) that takes a range of awaitables `p` and returns an awaitable capable on awaiting on one of the elements of `p`:
https://www.boost.org/doc/libs/develop/libs/cobalt/doc/html/index.html#race-...
<snip>
The library should list it as a *precondition* in the docs: the passed range cannot be empty. If you do it, you cannot expect your program to work correctly.
From the perspective of the library author, you can now, for the event of passing the empty range, putt all the tools that help in debugging: * BOOST_ASSERT * insert a breakpoint
And on the next line, you can also throw an exception to avoid an UB. But in that case the exception would not be part of the official contract.
If the docs just say "throws an exception" (as today, which I think is wrong) then the library author is not allowed to insert any debugging aids (such as BOOST_ASSERT or breaking into the debugger) because the user may be relying on the exception throw.
I agree with you. However, there are pros and cons of both approaches (UB for contract violation, or broader contract and defined behaviour with exception thrown). In all cases, "throw an exception" is not a sufficient specification. The contract must clearly state which exception is thrown. This is like the good old vector/array operator[] vs at(). There are use cases for both.
You do not want the users to rely on the exception throw: you want to prevent the users from passing the empty range.
What do others think about it?
I think that as much as possible should be done using the type system
and the compiler, because this is what is checked at compile time. In
that particular case, i think that:
template