6

Some functions should be non-throwing, but the standard doesn't say anything about it. Such as erase(q) (q denotes a valid dereferenceable constant iterator) of associative containers. This allows the implementation to throw any standard exception, according to [res.on.exception.handling#4]:

Functions defined in the C++ standard library that do not have a Throws: paragraph but do have a potentially-throwing exception specification may throw implementation-defined exceptions.170 Implementations should report errors by throwing exceptions of or derived from the standard exception classes ([bad.alloc], [support.exception], [std.exceptions]).

So if you want to swallow any implementation-defined exceptions they throw, you have to use a try-catch block.

std::set<int> s{1};
try
{
    s.erase(s.cbegin());
}
catch (...) {}

It's ugly and inefficient, but necessary. So I also don't know of any benefit to this.

  • Those functions have a narrow contracts, their input must match pre-conditions or the behavior is undefined. You aren't *meant* to pretend they will throw exceptions, but rather make sure their pre-conditions are met. – StoryTeller - Unslander Monica Aug 15 '22 at 10:57
  • @StoryTeller - Unslander Monica So if I meet the conditions, I can ignore whether they throw exceptions or not, and do whatever I want? – Blackteahamburger Aug 15 '22 at 11:27
  • 1
    If you meet the conditions, the functions needs to do what it's specified to do. I don't understand what you mean by "ignore" and "do whatever I want". Applications should handle exceptions, but wrapping *every* operation in a `try` is not the way to do it. – StoryTeller - Unslander Monica Aug 15 '22 at 11:32
  • @StoryTeller - Unslander Monica Thanks, I misunderstood what you mean. – Blackteahamburger Aug 15 '22 at 13:23
  • Please do not add answers to the question body itself. Instead, you should add it as an answer. [Answering your own question is allowed and even encouraged](https://stackoverflow.com/help/self-answer). – Adriaan Aug 18 '22 at 13:46
  • @Adriaan I just marked this part of the question as solved...I can't give an answer... – Blackteahamburger Aug 19 '22 at 01:23

1 Answers1

5

Here is a paper (quoted by some other standard library proposals) for reasons to not specify noexcept on some standard library functions: N3248: noexcept Prevents Library Validation

If a function that the standard says does not throw any exceptions does throw an exception, you have entered undefined behaviour territory. There is a bug in your code. Catching and swallowing it is certainly not the right thing to do.

For example in this code:

std::set<int> s;
try
{
    s.erase(s.cbegin());
}
catch (...) {}

may make s.erase throw a C++ exception in debug mode since the preconditions are not met (s.cbegin() is not dereferenceable), making this run and seem to work unnoticed, but suddenly some other behaviour happens (like a crash or an infinite loop) in release mode.

If the standard mandated that this function was noexcept, the function could not throw an exception even in debug mode.

However, standard libraries are allowed to add noexcept specifiers even if not explicitly stated in the standard, which many do. This gives the freedom for a standard library implementor to do what is appropriate (e.g., noexcept(true) in <release_set>, but noexcept(false) in <debug_set>)

And, of course, if you know the preconditions are met (like if (!s.empty()) s.erase(s.cbegin());), you know an exception can't be thrown and don't need to write any exception handling.

See also: Why vector access operators are not specified as noexcept?

Artyer
  • 31,034
  • 3
  • 47
  • 75
  • I understand. But you don't explain why erase(k) is missing noexcept, it doesn't cause undefined behavior. – Blackteahamburger Aug 15 '22 at 13:20
  • @Blackteahamburger I gave an example of where it does (giving it the end iterator, or giving it an iterator from another container, or maybe some UB somewhere else broke the internal structure of the set that is detected) – Artyer Aug 15 '22 at 14:46
  • I'm talking about erase(k) (k denotes a value), which never fails (even for a set broke by UB) because it always checks if the key exists. See my edit. – Blackteahamburger Aug 20 '22 at 05:02
  • @Blackteahamburger It could fail even if `<` was `noexcept` if `erase` detects that `<` does not form a valid total order (UB) or some other operation broke the container beforehand because of some other UB. Besides, when libraries are built in not-debug mode, they *are* usually marked `noexcept`. The standard allows but does not mandate it. – Artyer Aug 20 '22 at 15:19
  • Well, I agree with the first point, but I disagree with "some other operation broke the container beforehand because of some other UB." Because other functions like `begin()` and `end()` are also affected by the previous UB, but they are still `noexcept`. – Blackteahamburger Aug 21 '22 at 02:04
  • So, a more restrictive conditional `noexcept` specifier can be used. – Blackteahamburger Aug 21 '22 at 10:12
  • @Blackteahamburger It appears that [N3248](https://wg21.link/N3248) does not recommend removing `noexcept` in case of previous UB. But `erase` still does have the requirement that the object you pass is part of the weak ordering of your compare object. – Artyer Aug 21 '22 at 14:11