3

The standard library habitually allows for undefined behaviour if you break any requirements on template types, give erroneous function arguments, or any other breach of contract. Is it considered a good practise to allow this in user libraries? When is it fair to do so?

Consider writing an operator[] for a container:

template <typename t>
T& container<T>::operator[](int i)
{
  return internal_array[i];
}

If i indexes outside the bounds of the internal_array, we hit undefined behaviour. Should we allow this to happen or do bounds checking and throw an exception?

Another example is a function that takes an int argument but only allows a restricted domain:

int foo(int x)
{
  if (x > 0 && x <= 10) {
    return x;
  }
}

If x is not within the domain, execution will reach the end of the function without a return statement - this gives undefined behaviour.

Should a library developer feel bad for allowing this or not?

Joseph Mansfield
  • 108,238
  • 20
  • 242
  • 324
  • 1
    Might be a better fit for programmers, I don't know. – chris Feb 13 '13 at 23:00
  • Personally: If it's easy to give better diagnostics in the debug build (i.e. using asserts or something) I always go for it. Implementations of the standard library at least sometimes follow similar guidelines: `[]` often does bounds checking in debug builds. **But** there's never any reason to cause undefined behavior if you can avoid it at no additional runtime cost - if you're already doing the bounds checking give a good diagnostic. – Voo Feb 13 '13 at 23:01
  • @chris Maybe. I wasn't sure. – Joseph Mansfield Feb 13 '13 at 23:01
  • 1
    If its a libraries then you have to indicate to the user of the library to check the input themselves. – andre Feb 13 '13 at 23:03
  • It depends on the problem. I wouldn't add runtime checks to protect users who don't read documentation or can't be bothered with ensuring that they don't use erroneous data; that just slows everything down. – Pete Becker Feb 13 '13 at 23:03
  • 2
    Most programs would run just fine, performance-wise, if you added checks for everything. The reason libraries tend to default to just having no checks is that *you can add checks on top*; it's much harder to remove checks. – GManNickG Feb 13 '13 at 23:18
  • This question should be reopened. This is clearly a design decision that needs to be taken while programming, and thus is clearly on-topic on a programming Q&A. – David Rodríguez - dribeas Feb 14 '13 at 13:48
  • The library developer is not *causing* the UB; it's the user who steps outside the contract offered by the library. It is often too expensive to eliminate all UB from interfaces. Of course, the library should offer a clear and precise contract: which exception guarantees holds for which values of input, which inputs are validated, etc. But assuming that's in place (as it is for e.g. the integer shift `operator<<` and the container `operator[]`), the user is on his own. Caveat emptor. Note, that this does not apply to library code that itself contains UB, e.g. mulithreaded code with races. – TemplateRex Dec 27 '13 at 23:53
  • @DavidRodríguez-dribeas I agree that there is a valid question here provided it is worded correctly. As it stands, it is nonsensical: nobody ever allows undefined behavior anywhere. The standard allows implementors to *not* defined behavior in many circumstances. All API writers are able to do the same thing and this question asks when that is appropriate. – John McFarlane May 01 '19 at 11:36

2 Answers2

3

When is it fair to purposefully cause undefined behaviour?

Assuming you're asking this from the point of view of a library implementer: whenever you warn your client that failing to comply with the pre-conditions of a given function causes Undefined Behavior, and your client breaks those pre-conditions.

The C++11 Standard Library defines a lot of such functions: just think of the subscript operator for sequence collections.

If you're asking this from the viewpoint of an application programmer, on the other hand, the answer is of course "never", unless you are writing non-portable code that relies on some documented extension of your compiler and/or on some functionality of your Operating System (but then it's arguable whether you are still "talking C++").

Should a library developer feel bad for allowing this or not?

If that was the case, mr. Stepanov should feel horrible by now. No, it is not bad, it just depends on whether your library is designed for maximum efficiency or for maximum safety - with a lot of nuances in the middle.

Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
1

It all boils down to documentation.

Intuitively you wouldn't expect [] to do any bound checking, but you could also provide an at method to your container that throws an exception (yay, just like the folks that write the std). Of course you could throw an exception, but document this behavior.

The second is fair-play as well as long as you clearly document that the behavior is undefined if the function is called with the bad arguments and you have a good reason to do so (red on). If you're designing a performance-critical library, you don't want the overhead of checking input. If not, throw an exception.

Luchian Grigore
  • 253,575
  • 64
  • 457
  • 625
  • Personally I would consider a library that doesn't do simple bounds checking in a debug build (to stay with the example), badly designed. E.g. every `std` implementation I know has an assertion doing the bounds checking in `[]` for vectors - I don't even want to estimate how many off-by-1 bugs were found thanks to this. – Voo Feb 13 '13 at 23:10