[review][constrained_value] Review of Constrained Value Library begins today

Hi all,
The review of the Robert Kawulak's Constrained Value library begins today
December 1, 2008, and will end on December 10th -- I will be the review
manager. Please post reviews to the developer list.
Here's the library synopsis:
The Boost Constrained Value library contains class templates useful for
creating constrained objects. A simple example is an object representing an
hour of a day, for which only integers from the range [0, 23] are valid values.
bounded_int

On Mon, Dec 1, 2008 at 5:30 AM, Jeff Garland
The review of the Robert Kawulak's Constrained Value library begins today December 1, 2008, and will end on December 10th -- I will be the review manager. Please post reviews to the developer list.
Hi Robert, This seems like a very useful library, and after a cursory look of the documentation I feel that it has nicely well-rounded functionality. I have a couple of questions at this point (mostly extreme nit-picks about things that confused me when trying to think about how all I could use this library). In the basic definitions you have: "Constrained object is a wrapper for another object. It can be used just like the underlying object, with one exception: it can be assigned only a value which conforms to a specified constraint." ... "It can be used just like the underlying object": I have a suspicion that it can't be used "just like" the underlying object in all circumstances :-) I assume you can't call member functions of the underlying object if it's a class type (with the same syntax), or provide a constrained<int> as an argument to a function that takes an int &. Could you provide a slightly more precise explanation? The examples I looked at all use the constrained object in operator expressions. Is it that it can be used just like the underlying object in (most) operator expressions? In your wrapping iterator example you have: *((iter++).value()) ... so I assume you can't do *(iter++). (if so, why not?) "it can be assigned only a value which conforms to a specified constraint": when you say assigned, I'm thinking of the assignment operator, but you constrain more than that. Perhaps there is a more inclusive way of saying this? (maybe "it can only hold values which conform to a specified constraint"?) In your example "Object remembering its past extreme values", the policy is changing the constraint object directly. But, in your tutorial, you have: "Constraint of a constrained object cannot be accessed directly for modification, because the underlying value could become invalid according to the modified constraint. Therefore the constraint of a constrained object is immutable and change_constraint() function has to be used in order to modify the constraint. ..." Is the example violating how the library should be used? The value() function returns the underlying object by const &... so, I'm assuming that the constraint is not allowed to depend on any mutable parts of the underlying object's state? Thanks, Stjepan

Jeff Garland wrote:
bounded_int
::type hour; hour = 20; // OK hour = 26; // exception! Behavior in case of assignment of an invalid value can be customized.
I think this is wrong.
Providing an invalid value should be a non-recoverable programming error.
Non-recoverable means it should abort the program, and programming error
means it should only be enabled in debug mode.
i.e. an assert is the best choice to assert the preconditions are met.
Apart from that, wouldn't it be more interestin, when multiplying a
bounded_int

Mathias Gaunard wrote:
Jeff Garland wrote:
bounded_int
::type hour; hour = 20; // OK hour = 26; // exception! Behavior in case of assignment of an invalid value can be customized.
I think this is wrong. Providing an invalid value should be a non-recoverable programming error. Non-recoverable means it should abort the program, and programming error means it should only be enabled in debug mode. i.e. an assert is the best choice to assert the preconditions are met.
How about this then: hour = boost::lexical_cast<int>(user_input_string); Here user_input could come for example from a query string. It would be pointless to use the bounded_int class if I'd have to validate the user input myself. --> Mika Heiskanen

Quoting Mathias Gaunard
Jeff Garland wrote:
bounded_int
::type hour; hour = 20; // OK hour = 26; // exception! Behavior in case of assignment of an invalid value can be customized.
I think this is wrong. Providing an invalid value should be a non-recoverable programming error. Non-recoverable means it should abort the program, and programming error means it should only be enabled in debug mode. i.e. an assert is the best choice to assert the preconditions are met.
The custom error policy functionality supports this. So the only question is what the best default error policy is. I'm inclined to believe throwing is the best choice.

Mathias Gaunard wrote:
Jeff Garland wrote:
bounded_int
::type hour; hour = 20; // OK hour = 26; // exception! Behavior in case of assignment of an invalid value can be customized.
I think this is wrong. Providing an invalid value should be a non-recoverable programming error. Non-recoverable means it should abort the program, and programming error means it should only be enabled in debug mode. i.e. an assert is the best choice to assert the preconditions are met.
I disagree with your analysis. The '26' value could be a run-time calculation and not just a compile-time constant. In which case you are saying that the assignment of a run-time calculation should assert only when run in debug mode and otherwise should be ignored. I can not agree with that idea.
Apart from that, wouldn't it be more interestin, when multiplying a bounded_int
by 2, to yield a bounded_int instead of attempting to keep the object with the same constraint? Of course, that only works with bounded_int and not constraints in general.
I disagree with this also. The entire idea of a constraint in this library is that the underlying value meets the criteria of the constraint and not that the constraint changes to accomodate a new underlying value.

Edward Diener wrote:
Mathias Gaunard wrote:
Jeff Garland wrote:
bounded_int
::type hour; hour = 20; // OK hour = 26; // exception! Behavior in case of assignment of an invalid value can be customized.
I think this is wrong. Providing an invalid value should be a non-recoverable programming error. Non-recoverable means it should abort the program, and programming error means it should only be enabled in debug mode. i.e. an assert is the best choice to assert the preconditions are met.
I disagree with your analysis. The '26' value could be a run-time calculation and not just a compile-time constant. In which case you are saying that the assignment of a run-time calculation should assert only when run in debug mode and otherwise should be ignored. I can not agree with that idea.
Apart from that, wouldn't it be more interestin, when multiplying a bounded_int
by 2, to yield a bounded_int instead of attempting to keep the object with the same constraint? Of course, that only works with bounded_int and not constraints in general. I disagree with this also. The entire idea of a constraint in this library is that the underlying value meets the criteria of the constraint and not that the constraint changes to accomodate a new underlying value. I also disagree. There is no universal design here that would satisfy all needs. The only universal approach I see is that an operation on 2 operands of the same bi type should return the same bi type. If you want the result in a different type, cast the operands to that type first.

Edward Diener wrote:
Mathias Gaunard wrote:
Apart from that, wouldn't it be more interestin, when multiplying a bounded_int
by 2, to yield a bounded_int instead of attempting to keep the object with the same constraint? Of course, that only works with bounded_int and not constraints in general. I disagree with this also. The entire idea of a constraint in this library is that the underlying value meets the criteria of the constraint and not that the constraint changes to accomodate a new underlying value.
I have an even more extreme view on this. I don't believe any arithmetic operations should be permitted for constrained numbers. They just don't make sense (would you ever want to add two different days of a month?). There are occasions where you do want to perform an algorithm that does make sense but where arithmetic operations are performed along the way. But in those cases I want to be forced to do an explicit cast to an unconstrained type. I would add that I think the concept of constrained values is tightly linked to the concept of units (by which I mean the concept of wrapping a number type by a meaningful type), as well as the concept of an algebraic type (where you constrain the algebraic operations that can be performed). I would love to see someone put this all together into a single coherent library.

Deane Yang wrote:
Edward Diener wrote:
Mathias Gaunard wrote:
Apart from that, wouldn't it be more interestin, when multiplying a bounded_int
by 2, to yield a bounded_int instead of attempting to keep the object with the same constraint? Of course, that only works with bounded_int and not constraints in general. I disagree with this also. The entire idea of a constraint in this library is that the underlying value meets the criteria of the constraint and not that the constraint changes to accomodate a new underlying value.
I have an even more extreme view on this. I don't believe any arithmetic operations should be permitted for constrained numbers. They just don't make sense (would you ever want to add two different days of a month?).
You have chosen a particular type for which arithmetic operations sometimes make little sense. In many other situations this is not the case.
There are occasions where you do want to perform an algorithm that does make sense but where arithmetic operations are performed along the way. But in those cases I want to be forced to do an explicit cast to an unconstrained type.
It becomes inconvenient to use an explicit cast. It is more flexible that a constraint can transform to its underlying type whenever necessary, but still maintain the constraint after the operation occurs.
I would add that I think the concept of constrained values is tightly linked to the concept of units (by which I mean the concept of wrapping a number type by a meaningful type), as well as the concept of an algebraic type (where you constrain the algebraic operations that can be performed). I would love to see someone put this all together into a single coherent library.
There is already a units library in 1.37. Perhaps you want constraints added to this library.

Edward Diener wrote:
Deane Yang wrote:
I have an even more extreme view on this. I don't believe any arithmetic operations should be permitted for constrained numbers. They just don't make sense (would you ever want to add two different days of a month?).
You have chosen a particular type for which arithmetic operations sometimes make little sense. In many other situations this is not the case.
Could you give an example?

Deane Yang wrote:
Edward Diener wrote:
Deane Yang wrote:
I have an even more extreme view on this. I don't believe any arithmetic operations should be permitted for constrained numbers. They just don't make sense (would you ever want to add two different days of a month?).
You have chosen a particular type for which arithmetic operations sometimes make little sense. In many other situations this is not the case.
Could you give an example?
An example of what ? Of a constraint where arithmetic operations make sense ? If we take a constraint representing a percentage between 0 and 100, I think it is perfectly valid to add a percent to what is already there. That is just one of innumerable cases where arithmetic operations of contraints seem perfectly valid to me. Why would you even want to consider limiting constraints by removing arithmetic operations if the underlying type supports such operations ?

Deane Yang wrote:
Edward Diener wrote:
Deane Yang wrote:
I have an even more extreme view on this. I don't believe any arithmetic operations should be permitted for constrained numbers. They just don't make sense (would you ever want to add two different days of a month?).
You have chosen a particular type for which arithmetic operations sometimes make little sense. In many other situations this is not the case.
Could you give an example?
One example: adding 2 to Monday to get Wednesday, or adding 2 to Saturday to get Monday. Joe Gottman

Joe Gottman wrote:
Deane Yang wrote:
Edward Diener wrote:
Deane Yang wrote:
I have an even more extreme view on this. I don't believe any arithmetic operations should be permitted for constrained numbers. They just don't make sense (would you ever want to add two different days of a month?).
You have chosen a particular type for which arithmetic operations sometimes make little sense. In many other situations this is not the case.
Could you give an example?
One example: adding 2 to Monday to get Wednesday, or adding 2 to Saturday to get Monday.
But that does not correspond to the built-in arithmetic operation. This is modular arithmetic in disguise, so wouldn't make more sense to use a modular integer class (integers mod 7) and represent the days of the week using the numbers 0 to 6?

Edward Diener wrote:
I disagree with your analysis. The '26' value could be a run-time calculation and not just a compile-time constant. In which case you are saying that the assignment of a run-time calculation should assert only when run in debug mode and otherwise should be ignored. I can not agree with that idea.
I say it is up to the programmer to ensure valid values are provided. If the value is only known at runtime, there should be explicit checking in the code prior to assignment.

Mathias Gaunard wrote:
I say it is up to the programmer to ensure valid values are provided. If the value is only known at runtime, there should be explicit checking in the code prior to assignment.
If the value must be checked beforehand, why bother using a constrained type at all? Where's the benefit if all assignments are already guaranteed to be valid by external tests? Might just as well use a test+POD combination. --> Mika Heiskanen

Mathias Gaunard wrote:
Mika Heiskanen wrote:
If the value must be checked beforehand, why bother using a constrained type at all?
Because it performs checking in debug mode to make sure the invariant is indeed true.
So, the goal of a constrained value is to check the same thing twice while in Debug mode, even though it will still be checked elsewhere in both debug and release modes? What happens when someone doing maintenance gets these two separate checks for the same thing out of sync? I see how your model can cause extra problems, but I don't see how it makes anything easier. John

-------- Original-Nachricht --------
Datum: Thu, 04 Dec 2008 17:37:46 +0100 Von: Mathias Gaunard
An: boost-users@lists.boost.org CC: boost@lists.boost.org Betreff: Re: [Boost-users] [review][constrained_value] Review of Constrained Value Library begins today
Mika Heiskanen wrote:
If the value must be checked beforehand, why bother using a constrained type at all?
Because it performs checking in debug mode to make sure the invariant is indeed true.
Not always is this enough. Especially when you get input from datasources (DB, Sensors, Cameras) you can not always be sure, it fits your needs. I think this all should be handled with an error policy, as there a lot of usecases, which differ from their needs for errorchecking and handling widely. regards Jens Weller
_______________________________________________ Boost-users mailing list Boost-users@lists.boost.org http://lists.boost.org/mailman/listinfo.cgi/boost-users
-- Psssst! Schon vom neuen GMX MultiMessenger gehört? Der kann`s mit allen: http://www.gmx.net/de/go/multimessenger

Mathias Gaunard wrote:
Mika Heiskanen wrote:
If the value must be checked beforehand, why bother using a constrained type at all?
Because it performs checking in debug mode to make sure the invariant is indeed true.
I do not believe boost should accept a library which would only work in debug mode. I believe boost is mostly about runtime libraries, not about debugging aids. --> Mika Heiskanen

Mika Heiskanen skrev:
Mathias Gaunard wrote:
Mika Heiskanen wrote:
If the value must be checked beforehand, why bother using a constrained type at all?
Because it performs checking in debug mode to make sure the invariant is indeed true.
I do not believe boost should accept a library which would only work in debug mode.
How different users want to respond to errors should not be mandated by the library. Therefore it is very good that this library allows you to customize it. There is a difference between protecting against accidental misuse and deliberate misuse of a component. Why can rarely protect against the latter. Having invariants checked only in debug mode is very common, FWIW. -Thorsten

Thorsten Ottosen wrote:
Mika Heiskanen skrev:
Mathias Gaunard wrote:
Mika Heiskanen wrote:
If the value must be checked beforehand, why bother using a constrained type at all?
Because it performs checking in debug mode to make sure the invariant is indeed true.
I do not believe boost should accept a library which would only work in debug mode.
How different users want to respond to errors should not be mandated by the library. Therefore it is very good that this library allows you to customize it.
There is a difference between protecting against accidental misuse and deliberate misuse of a component. Why can rarely protect against the latter.
Having invariants checked only in debug mode is very common, FWIW.
I did not express my thoughts very well the last time. Retry: I am not against having invariants checked only in debug mode, if the user so chooses. I am however against it being the default mode. I believe the most sensible default would be to throw an exception both in debug and release modes, since I believe this to be the most common use case. --> Mika Heiskanen

Mika Heiskanen wrote:
I am not against having invariants checked only in debug mode, if the user so chooses. I am however against it being the default mode. I believe the most sensible default would be to throw an exception both in debug and release modes, since I believe this to be the most common use case.
Exceptions should almost never be used in response to broken invariants. -- Dave Abrahams BoostPro Computing http://www.boostpro.com

David Abrahams wrote:
Exceptions should almost never be used in response to broken invariants.
Pardon my ignorance, but how come? The constraints I have to validate usually concern broken user input via the command line or a query string. How should I deal with them if not with exceptions? Or to be more precise, why should I not use constrained types for validating user input if it saves me a lot of if-statements in the long run? --> Mika Heiskanen

on Sat Dec 20 2008, Mika Heiskanen
David Abrahams wrote:
Exceptions should almost never be used in response to broken invariants.
Pardon my ignorance, but how come? The constraints I have to validate usually concern broken user input
That's not a broken invariant. A broken invariant means your program is broken.
via the command line or a query string. How should I deal with them if not with exceptions? Or to be more precise, why should I not use constrained types for validating user input if it saves me a lot of if-statements in the long run?
I would never argue that you shouldn't do that. -- Dave Abrahams BoostPro Computing http://www.boostpro.com

David Abrahams wrote:
on Sat Dec 20 2008, Mika Heiskanen
wrote: David Abrahams wrote:
Exceptions should almost never be used in response to broken invariants. Pardon my ignorance, but how come? The constraints I have to validate usually concern broken user input
That's not a broken invariant. A broken invariant means your program is broken.
via the command line or a query string. How should I deal with them if not with exceptions? Or to be more precise, why should I not use constrained types for validating user input if it saves me a lot of if-statements in the long run?
I would never argue that you shouldn't do that.
So you see why I would prefer the constrained value class being reviewed to throw? Also, pardon my ignorance again, but would you care to explain how an expert would handle broken invariants in broken programs if not via exceptions? I was not aware there is a better solution. Regards, --> Mika Heiskanen

Hi Mika,
Also, pardon my ignorance again, but would you care to explain how an expert would handle broken invariants in broken programs if not via exceptions? I was not aware there is a better solution.
I guess David means to distinguish between run-time errors and invariants failures. Run-time errors are expected conditions. An attempt to open a file may fail. An attempt to connect to a remote host over the network may fail. Your program is supposed to anticipate those conditions. Run-time errors may or may not be signalled by exceptions, depending on whether stack unwinding is desirable or not. Invariant failures, however, are unexpected conditions. Invariants are not supposed to fail. If they do, it's a sign of incorrect program logic or faulty assumptions. Your program will generally not be able to recover from such an error, hence invariant checking functions like assert() typically abort the process on failure (and generate debugging information, such as a core dump). I hope this helps, Peter

Hello Peter, Peter Simons wrote:
Invariant failures, however, are unexpected conditions. Invariants are not supposed to fail. If they do, it's a sign of incorrect program logic or faulty assumptions. Your program will generally not be able to recover from such an error, hence invariant checking functions like assert() typically abort the process on failure (and generate debugging information, such as a core dump).
Thank you for your explanation Peter. However, I do not see why an assert should be the first choice when a programming error can be detected by the program itself. For example, I would prefer my word processor to announce a programming error instead of producing a core dump. Am I missing some finer point on the nature of invariants? --> Mika Heiskanen

Hi Mika,
I would prefer my word processor to announce a programming error instead of producing a core dump. Am I missing some finer point on the nature of invariants?
apparently, you expect invariants to fail. They won't. Invariants never, ever fail. They are invariant. These assumptions are the cornerstones on which your algorithms are built. Adding code that throws an exception in case of an invariant failure is wasted, because that exception will never be thrown. Invariants are checked only during development. Release binaries typically don't contain those tests at all, i.e. assert() is disabled by compiling with the preprocessor symbol NDEBUG defined. Take care, Peter

Peter Simons wrote: Hello Peter,
Hi Mika,
I would prefer my word processor to announce a programming error instead of producing a core dump. Am I missing some finer point on the nature of invariants?
apparently, you expect invariants to fail. They won't. Invariants never, ever fail. They are invariant. These assumptions are the cornerstones on which your algorithms are built. Adding code that throws an exception in case of an invariant failure is wasted, because that exception will never be thrown.
No, I do not except them to fail. However, it is possible that they fail due to programming errors, and I would be naive to assume none would make it to release versions. For example, I cannot let my server crash if it cannot fulfill a particular type of request due to a programming error. Instead it should log the error and fail that particular request. Clearly the matter should have been resolved in advance by sufficient testing, but is any amount of testing really ever sufficient? I guess the problem is that I believe invariant failures may be recoverable, but it does not seem to be the concensus. Perhaps I am thinking too much about servers which must not crash. Kind Regards, --> Mika Heiskanen

From: Mika Heiskanen
For example, I cannot let my server crash if it cannot fulfill a particular type of request due to a programming error. Instead it should log the error and fail that particular request.
If one invariant fails, this means your program is not reliable anymore (also when processing subsequent requests). Other invariants may become invalid too and any assumptions made during writing of your program may not hold anymore. This means that the program will most probably start behaving in an unexpected way. I think in most cases it is preferred to crash the program than allow it to produce invalid output and, for example, store it in a database.

Robert Kawulak wrote:
From: Mika Heiskanen
For example, I cannot let my server crash if it cannot fulfill a particular type of request due to a programming error. Instead it should log the error and fail that particular request.
If one invariant fails, this means your program is not reliable anymore (also when processing subsequent requests). Other invariants may become invalid too and any assumptions made during writing of your program may not hold anymore. This means that the program will most probably start behaving in an unexpected way. I think in most cases it is preferred to crash the program than allow it to produce invalid output and, for example, store it in a database.
Wikipedia definition for an invariant: "In computer science, a predicate that, if true, will remain true throughout a specific sequence of operations, is called (an) invariant to that sequence." So it seems all the confusion is due to my misunderstanding of what an invariant is. The simple definition I knew until now was that an invariant is something that does not change value. What I was missing was that it also has to satisfy the constraints before you can actually call it an invariant, and hence it makes no sense to test the constraints again once you can call something an invariant. Note for example that the page http://en.wikipedia.org/wiki/Invariant says "Invariant (computer science), an expression whose value doesn't change during program execution." IMO that definition means something completely different from the definition with the predicate included. I believe the confusion is thus resolved, sorry for the noise. Kind regards, --> Mika Heiskanen

Hi Mika,
No, I do not except [invariants] to fail. However, it is possible that they fail due to programming errors, and I would be naive to assume none would make it to release versions.
invariants as implemented by assert() have been around for decades and were introduced into the C programming language by the likes of Brian Kernighan and Dennis Ritchie. Since then, that functionality has been used in billions of lines of code written by thousands of people from all over the world. Personally, I would hesitate to take on a perspective that requires me to believe that all those people are naive. Say you write a function that performs some integer arithmetic and at some point you call abs(): int i = ... ; int j = std::abs(i); assert(j >= 0); This is an invariant. There is NO WAY "j" is going to be less than zero, right? Of course, your compiler might be broken or your standard library might be broken or your CPU might be broken or your memory chips might be broken, and that is why checking invariants is potentially beneficial during software development. Another example is this one: T * ptr = new T; assert(ptr); If "new" would fail, you'd get an exception, so clearly there is now way "ptr" is going to be NULL, right? So let's say "ptr" *is* NULL for some reason. What would you like to do about it? How would you like to recover from such a fundamental problem? And furthermore, would you like to perform that "ptr != 0" comparison in your program every time you allocate an object? Would you like to compare "j >= 0" every time you use std::abs()? What if that computation is performed inside of loop that's being executed billions of times? For debugging purposes, these checks might be useful, but in production code these kind of checks are a waste of time. Now, there is another kind of check: void * ptr = std::malloc(1024); if (ptr == 0) throw std::runtime_error("bad malloc"); To use an assert() at this point would be a mistake, because malloc() can fail, and it will fail. The result "ptr == 0" is a perfectly valid state of this program and not to consider this state would be a bug. Take care, Peter

Peter Simons wrote:
Hi Mika,
No, I do not except [invariants] to fail. However, it is possible that they fail due to programming errors, and I would be naive to assume none would make it to release versions.
invariants as implemented by assert() have been around for decades and were introduced into the C programming language by the likes of Brian Kernighan and Dennis Ritchie. Since then, that functionality has been used in billions of lines of code written by thousands of people from all over the world. Personally, I would hesitate to take on a perspective that requires me to believe that all those people are naive.
As noted in a previous reply, something was lost in translation. References: http://en.wikipedia.org/wiki/Invariant http://en.wikipedia.org/wiki/Invariant_(computer_science) I'm sure you agree the definitions in the above links differ significantly. I do believe I would have been correct according to the former definition, but not according to the latter more precise definition. Having read the latter definition everything makes perfect sense, and all the responses to this thread, including yours, are perfectly correct. A single word (predicate) can make a huge difference, and obviously I could have saved everybody some irritation had I known the exact CS definition instead of the general translation. I did not mean to aggrevate anyone by not understanding what I was missing. Reminds me of Dennett's "Darwin's dangerous idea" with the game of inventing how much damage you can inflict with a single typographical change: "the *w*ife of man, solitary, poore, nasty, brutish and short" (pp 110). Kind regards, --> Mika Heiskanen

on Sun Dec 21 2008, Mika Heiskanen
I guess the problem is that I believe invariant failures may be recoverable, but it does not seem to be the concensus.
They may be, but they may not be, and you can't know which, since your program wasn't designed to behave the way it has behaved. Trying to recover can do more damage than good in such a scenario.
Perhaps I am thinking too much about servers which must not crash.
For such servers there are other approaches, like starting up a fresh process and shutting down the current one. But all this is covered in that thread I referred to: http://groups.google.com/group/comp.lang.c++.moderated/browse_frm/thread/800... -- Dave Abrahams BoostPro Computing http://www.boostpro.com

Mika Heiskanen skrev:
Hello Peter,
Peter Simons wrote:
Invariant failures, however, are unexpected conditions. Invariants are not supposed to fail. If they do, it's a sign of incorrect program logic or faulty assumptions. Your program will generally not be able to recover from such an error, hence invariant checking functions like assert() typically abort the process on failure (and generate debugging information, such as a core dump).
Thank you for your explanation Peter. However, I do not see why an assert should be the first choice when a programming error can be detected by the program itself. For example, I would prefer my word processor to announce a programming error instead of producing a core dump. Am I missing some finer point on the nature of invariants?
That choice is also perfectly valid, and can be easily achieved with your own assert macro/error handler function. However, checking many preconditions/postconditions and invariants at runtime can be very expensive. http://www.artima.com/cppsource/deepspace.html might be interesting to read. -Thorsten

From: Mika Heiskanen
Exceptions should almost never be used in response to broken invariants. Pardon my ignorance, but how come? The constraints I have to validate usually concern broken user input
That's not a broken invariant. A broken invariant means your program is broken.
via the command line or a query string. How should I deal with them if not with exceptions? Or to be more precise, why should I not use constrained types for validating user input if it saves me a lot of if-statements in the long run?
I would never argue that you shouldn't do that.
So you see why I would prefer the constrained value class being reviewed to throw?
Also, pardon my ignorance again, but would you care to explain how an expert would handle broken invariants in broken programs if not via exceptions? I was not aware there is a better solution.
There seems to be confusion between broken invariant and assignment of invalid value. The latter is handled by the error policy and it is its duty to prevent breaking the invariant. One of the ways to prevent it is throwing an exception, other ones are adjusting the value to be valid or ignoring the assignment. If the invariant gets broken, then it means there's something wrong either with your error policy, the constraint policy or the value type, because they do not fulfil the requirements of constrained class. So, as David has pointed out, broken invariant means that the programmer has done something wrong. Best regards, Robert

Robert Kawulak wrote:
There seems to be confusion between broken invariant and assignment of invalid value. The latter is handled by the error policy and it is its duty to prevent breaking the invariant. One of the ways to prevent it is throwing an exception, other ones are adjusting the value to be valid or ignoring the assignment. If the invariant gets broken, then it means there's something wrong either with your error policy, the constraint policy or the value type, because they do not fulfil the requirements of constrained class. So, as David has pointed out, broken invariant means that the programmer has done something wrong.
The confusion is all mine, I'm sure. However, I still do not see why a programming error should not be handled by an exception if it is possible. Of course some situations are so severe that the error is unrecoverable, but is that really the most common case with *detectable* programming errors? Regards, --> Mika Heiskanen

on Sun Dec 21 2008, Mika Heiskanen
David Abrahams wrote:
on Sat Dec 20 2008, Mika Heiskanen
wrote: David Abrahams wrote:
Exceptions should almost never be used in response to broken invariants. Pardon my ignorance, but how come? The constraints I have to validate usually concern broken user input
That's not a broken invariant. A broken invariant means your program is broken.
via the command line or a query string. How should I deal with them if not with exceptions? Or to be more precise, why should I not use constrained types for validating user input if it saves me a lot of if-statements in the long run?
I would never argue that you shouldn't do that.
So you see why I would prefer the constrained value class being reviewed to throw?
No. In general libraries, invalid user input should be reported via a different mechanism, because there's no reason to believe that the condition should be handled far from the site where validation is requested. If you decide that throwing is an appropriate response for your application, you can do that in a layer outside the validation.
Also, pardon my ignorance again, but would you care to explain how an expert would handle broken invariants in broken programs if not via exceptions? I was not aware there is a better solution.
Stop the program immediately. If appropriate, execute emergency shutdown measures. Do not unwind stack, do not pass "Go," do not collect $200. ;-) -- Dave Abrahams BoostPro Computing http://www.boostpro.com

on Sat Dec 20 2008, Mika Heiskanen
David Abrahams wrote:
Exceptions should almost never be used in response to broken invariants.
Pardon my ignorance, but how come?
I suggest reading through my posts in http://groups.google.com/group/comp.lang.c++.moderated/browse_frm/thread/800... which give the arguments in detail. Regards, -- Dave Abrahams BoostPro Computing http://www.boostpro.com

David Abrahams wrote:
I suggest reading through my posts in http://groups.google.com/group/comp.lang.c++.moderated/browse_frm/thread/800... which give the arguments in detail.
Thank you for the link, I read the thread from start to finish. It was not clear to me at the end that any agreement was actually reached, in particular with respect to terminology. Wikipedia says: a) In computer science, a predicate that, if true, will remain true throughout a specific sequence of operations, is called (an) invariant to that sequence. b) In computer programming, a precondition is a condition or predicate that must always be true just prior to the execution of some section of code or before an operation in a formal specification. So, an invariant is a value which has already satified a predicate, and if it no longer satisfies one, a logic error must have occurred. On the other hand a precondition is something that must be true before being able to execute some section of code. I can se why an invariant failure should abort, but not why a precondition failure should. In particular, Wikipedia says: If a precondition is violated, the effect of the section of code becomes undefined and thus may or may not carry out its intended work. So, if were never to execute the section of code, the state could remain perfectly well defined. The definition does not seem to imply that the state must already be undefined. Is this the correct terminology? In the link you gave you seem to have the opinion that a precondition failure is generally fatal, but according to the Wikipedia definition it may not be if you never execute the code. These are separate quotes from your posts in the link you gave: If preconditions are broken, your program state is broken, by definition. Trying to recover is generally ill-advised. Go back to the definition of precondition from Wikipedia: the consequence is undefined behavior. Which is correct? Does a precondition violation always cause undefined behaviour, or only if the preconditioned code is executed? Should I substitute "invariant" for "precondition" in the above quotes? Sorry if I am being a nuisance. I thought I had already understood the terminology, but reading the thread you mentioned made it a fuzzy again. --> Mika Heiskanen

on Tue Dec 23 2008, Mika Heiskanen
David Abrahams wrote:
I suggest reading through my posts in
http://groups.google.com/group/comp.lang.c++.moderated/browse_frm/thread/800...
which give the arguments in detail.
Thank you for the link, I read the thread from start to finish. It was not clear to me at the end that any agreement was actually reached, in particular with respect to terminology.
Wikipedia says:
a) In computer science, a predicate that, if true, will remain true throughout a specific sequence of operations, is called (an) invariant to that sequence.
b) In computer programming, a precondition is a condition or predicate that must always be true just prior to the execution of some section of code or before an operation in a formal specification.
I don't care to aargue about terminology, but those two definitions are certainly compatible.
So, an invariant is a value which has already satified a predicate, and if it no longer satisfies one, a logic error must have occurred. On the other hand a precondition is something that must be true before being able to execute some section of code. I can se why an invariant failure should abort, but not why a precondition failure should.
Detecting either a broken supposed-invariant or a failed precondition means there's a logic error in the program and state may already be arbitrarily corrupt.
In particular, Wikipedia says:
If a precondition is violated, the effect of the section of code becomes undefined and thus may or may not carry out its intended work.
So, if were never to execute the section of code, the state could remain perfectly well defined.
The state set up by the calling code must in general be assumed to be already broken. Precondition violations don't just come about because the immediate caller isn't obeying the rules. The immediate caller may have done everything correctly.
The definition does not seem to imply that the state must already be undefined.
There's no such thing as undefined state. The behavior of the code is undefined.
Is this the correct terminology? In the link you gave you seem to have the opinion that a precondition failure is generally fatal, but according to the Wikipedia definition it may not be if you never execute the code.
These are separate quotes from your posts in the link you gave:
If preconditions are broken, your program state is broken, by definition. Trying to recover is generally ill-advised.
Go back to the definition of precondition from Wikipedia: the consequence is undefined behavior.
Which is correct?
Both
Does a precondition violation always cause undefined behaviour,
Yes
or only if the preconditioned code is executed?
If the code isn't executed, the precondition doesn't apply. There's a precondition on integer division that the divisor be nonzero. If you call a variable "divisor" and never actually use it as a divisor, there's no precondition violation. If you set preconditions on your function, they apply to your callers, and thus are true or not at its interface, not to some point during the execution after you've done some checking.
Should I substitute "invariant" for "precondition" in the above quotes?
No
Sorry if I am being a nuisance. I thought I had already understood the terminology, but reading the thread you mentioned made it a fuzzy again.
Hope this helped. -- Dave Abrahams BoostPro Computing http://www.boostpro.com

Mika Heiskanen wrote:
David Abrahams wrote:
I suggest reading through my posts in http://groups.google.com/group/comp.lang.c++.moderated/browse_frm/thread/800... which give the arguments in detail.
Thank you for the link, I read the thread from start to finish. It was not clear to me at the end that any agreement was actually reached, in particular with respect to terminology.
I also read the thread from start to finish. About halfway through the thread, I suddenly could follow the arguments given by David Abrahams, and he seems to be right. But now std::logic_error suddenly seems to make no sense any longer. Is this a valid conclusion from reading that thread? Then I reviewed my past issues with using exceptions in the light of this new insight. My worst issues with exceptions occured in connection with OpenMP and MPI. Because I used exceptions as a way to report errors to the user, I wanted them to propagate upwards out of the parallel regions. But because the exceptions don't occur synchronous in all workers, this just messed up the control flow. And neither OpenMP nor MPI try to help the programmer with the task of synchronising exceptions and propagating them upwards. However, as soon as I issue exceptions only in exceptional circumstances, the whole mess becomes much more managable again. So a manageable solution would be to handle "valid" user errors before entering the parallel sections of the programm, handle the few remaining "valid" exceptions (like out of memory conditions) by explicit communication between the workers, and don't try to recover from "logic errors" inside the parallel sections. This may force me to structure my (parallel) algorithms such that they have explicit resource allocation phases (during which no communication occurs, but exceptions are allowed), and running phases (during which no exceptions are allowed, but communication may occur). Does this solution makes sense? Is there a better solution for this problem?
Sorry if I am being a nuisance. I thought I had already understood the terminology, but reading the thread you mentioned made it a fuzzy again.
Sorry for misusing this thread. But reading through the above thread took me quite some time (yet the new insight was worth it), so now I'm eager to get some answers to the questions and thoughts that this thread induced. Regards, Thomas

Thomas Klimpel wrote:
So a manageable solution would be to handle "valid" user errors before entering the parallel sections of the programm, handle the few remaining "valid" exceptions (like out of memory conditions) by explicit communication between the workers, and don't try to recover from "logic errors" inside the parallel sections. This may force me to structure my (parallel) algorithms such that they have explicit resource allocation phases (during which no communication occurs, but exceptions are allowed), and running phases (during which no exceptions are allowed, but communication may occur). Does this solution makes sense? Is there a better solution for this problem?
I usually try to follow the structure you described. However, previously I had seen no serious harm in throwing inside the running section even if I knew I had done the same test in the first phase. David Abrahams wrote:
Detecting either a broken supposed-invariant or a failed precondition means there's a logic error in the program and state may already be arbitrarily corrupt.
Thank you, this pretty much nails it for me. I do believe http://en.wikipedia.org/wiki/Precondition is more lenient in the definition. In particular this sentence If a precondition is violated, the effect of the section of code becomes undefined and thus may or may not carry out its intended work. seems to allow more for a chance of recovery. David's "state may already be arbitrarily corrupt" would make a big difference in conveying the seriousness of the situation. Regards, --> Mika Heiskanen

on Tue Dec 23 2008, "Thomas Klimpel"
Mika Heiskanen wrote:
David Abrahams wrote:
I suggest reading through my posts in
http://groups.google.com/group/comp.lang.c++.moderated/browse_frm/thread/800...
which give the arguments in detail.
Thank you for the link, I read the thread from start to finish. It was not clear to me at the end that any agreement was actually reached, in particular with respect to terminology.
I also read the thread from start to finish. About halfway through the thread, I suddenly could follow the arguments given by David Abrahams, and he seems to be right. But now std::logic_error suddenly seems to make no sense any longer. Is this a valid conclusion from reading that thread?
IMO std::logic_error never made much sense for C++.
Then I reviewed my past issues with using exceptions in the light of this new insight. My worst issues with exceptions occured in connection with OpenMP and MPI. Because I used exceptions as a way to report errors to the user, I wanted them to propagate upwards out of the parallel regions. But because the exceptions don't occur synchronous in all workers, this just messed up the control flow. And neither OpenMP nor MPI try to help the programmer with the task of synchronising exceptions and propagating them upwards. However, as soon as I issue exceptions only in exceptional circumstances,
"in exceptional circumstances" has a nice ring to it, but it doesn't mean very much in actuality. I suggest you phrase your criterion moree precisely so you know what you've actually discovered. My criterion is, "issue an exception when a function cannot meet its postcondition."
the whole mess becomes much more managable again. So a manageable solution would be to handle "valid" user errors before entering the parallel sections of the programm, handle the few remaining "valid" exceptions (like out of memory conditions) by explicit communication between the workers, and don't try to recover from "logic errors" inside the parallel sections.
Don't try to recover from logic errors, period, unless you have some kind of "state firewall" like a process boundary that allows you to be very sure that the rest of the system is OK. In the case of MPI, I even a process boundary might not be enough.
This may force me to structure my (parallel) algorithms such that they have explicit resource allocation phases (during which no communication occurs, but exceptions are allowed), and running phases (during which no exceptions are allowed, but communication may occur).
I haven't thought about this very much in the context of MPI, but I've been doing some MPI programming myself recently, and IIUC it's important for all the processes to be following roughly the same code path. So if you get an exception in one process but not the others, it could be easy to confuse your parallel programs unless you do something to account for the problem.
Sorry if I am being a nuisance. I thought I had already understood the terminology, but reading the thread you mentioned made it a fuzzy again.
Sorry for misusing this thread. But reading through the above thread took me quite some time (yet the new insight was worth it), so now I'm eager to get some answers to the questions and thoughts that this thread induced.
Well, this is a good point; the whole thing is a bit off-topic for Boost, unless we make it about how Boost.MPI might better support exception handling. -- Dave Abrahams BoostPro Computing http://www.boostpro.com

Mathias Gaunard wrote:
Edward Diener wrote:
I disagree with your analysis. The '26' value could be a run-time calculation and not just a compile-time constant. In which case you are saying that the assignment of a run-time calculation should assert only when run in debug mode and otherwise should be ignored. I can not agree with that idea.
I say it is up to the programmer to ensure valid values are provided. If the value is only known at runtime, there should be explicit checking in the code prior to assignment.
Suppose the value is provided by the end user of the program. Do you still feel such a value, when used in a constraint, should assert only in debug mode rather than throw an exception ? A large part of the constrained value concept is that the constraint provides the necessary checking, in the form of a policy, which alleviates specific pre-checking by the programmer. I like that, else the constraint library serves less purpose than it could.

Edward Diener wrote:
Suppose the value is provided by the end user of the program.
Then the programmer should check its value before assigning it into the constrained type, like I just said. A constrained type is just a type that must maintain an invariant, and it should be up to the programmer to ensure that the necessary preconditions for the invariant are always met, and assertions are here to help him do just that.
A large part of the constrained value concept is that the constraint provides the necessary checking, in the form of a policy, which alleviates specific pre-checking by the programmer. I like that, else the constraint library serves less purpose than it could.
To me, that makes it useless, because that's adding runtime checks where it is not always needed, potentially adding quite some overhead. At least it's configurable, but the default behaviour isn't the right one IMO.

Mathias Gaunard wrote:
I think this is wrong. Providing an invalid value should be a non-recoverable programming error. Non-recoverable means it should abort the program, and programming error means it should only be enabled in debug mode. i.e. an assert is the best choice to assert the preconditions are met.
I couldn't disagree more: that would negate the whole point of the library. I'm not saying that the design choices you want may not be valid in some situations, just that you're asking for a different library. Indeed if it's absolutely essential to the proper functioning of the software that a value fall within certain constraints, turning off assertions in release mode, or indeed aborting, may not be viable options (think embedded program control software). Would you really want your software to crash if it detected a negative velocity for your car? (or your car to crash if the software fell over?). This library lets you choose what actions happen on constraint-failure, including assert or no-op actions if that's what you really want. But I suspect most users of the library will be looking for something that will allow them to write fault-tolerant code.
Apart from that, wouldn't it be more interestin, when multiplying a bounded_int
by 2, to yield a bounded_int instead of attempting to keep the object with the same constraint?
Interesting, as in "may you live in interesting times"? ;-) John.

Here is my review for the proposed library:
- What is your evaluation of the design?
I think the design is simple, elegant, and allows a lot of flexibility. The only problem I see is that it doesn't address the frequently requested support for floating point constraints. And with that, the problem is not in the implementation-related aspects of the design, but in what the library sets out as requirements for the behavior of the constraint. I think permitting constraints that can "spontaneously" switch from unsatisfied to satisfied (and documenting what that means in terms of guarantees the library makes) would be a good thing. One thing I want to mention is that there is a slightly higher abstraction that this library almost addresses, what can maybe be called "monitored values" - values where you need to do something when the value changes (or is first constructed). In the case of this library, "something" is checking a constraint and calling a policy if the constraint is violated. In other cases, it might be calling a boost.signal, in another, writing to a log. The author might consider (eventually) altering the design of the library to offer this higher level of abstraction, as he already solves the problem of hooking into a value change reasonably well (so, in addition to constrained and bounded classes, offer a monitored class using which constrained is implemented). This would allow much wider use of the library. (I don't know if there are requirements for a "MonitoredValue" library that are significantly different / in addition to what the current libraries, that I'm not thinking of at the moment)
- What is your evaluation of the implementation?
I looked at the implementation of the constrained class, and found it very satisfactory. Well implemented, well documented code that was a pleasure to review.
- What is your evaluation of the documentation?
Great as a tutorial, and great as class / function reference. The only part I found lacking is the discussion of exact requirements on the underlying value type / constraint (my previous posts elaborate on this). Robert - I think the notion of "spontaneous" changes of the constraint extends beyond the floating point case and perhaps be used to discus other "spontaneous" changes or changes beyond the control of whatever the library has control over (like the use_count of a shared_ptr).
- What is your evaluation of the potential usefulness of the library?
Very useful. Beyond the constrained and wrapping built-in types, the documentation suggests some very creative uses of the library, and the flexibility of the library promises a lot more. When it can take advantage of declspec, the usefulness will further increase even more. Lambda might kick it up a notch as well (Robert, in my suggestion to provide syntactic sugar support for the calling of a f(value_type &) function, my unstated point was that f could be any callable - including a lambda expression).
- Did you try to use the library? With what compiler? Did you have any problems?
Nope. No problems :-)
- How much effort did you put into your evaluation? A glance? A quick reading? In-depth study?
I read through the documentation a few times, followed the discussion on the list, and looked at the implementation of the constrained class.
- Are you knowledgeable about the problem domain?
My experience in using functionality related to the library consists of inserting asserts here and there, and manually wrapping values when they need to wrap. I recall plenty of situations where having a library like this would have been wonderful.
Please state in your review, whether you think the library should be accepted as a Boost library.
I believe the library should be accepted. I found the library quite feature-complete for its domain (in reading the docs, every time I found myself thinking "this library should support x", a few paragraphs down I would find out it does), while preserving simplicity and elegance. Some of the things I would definitely like to see in the final version are: * document exact requirements on the value, constraint and policy types, and how they relate to library's guarantees * the library should *legally* allow for a reasonable floating point solution (it would be great if it actually provided one, but allowing one would be good enough for me) * (seconding suggestions by other reviewers): provide tests (including sizeof tests) Nice work, Robert! Stjepan

There is confusion over bounded vs. bounded_int. Maybe rename bounded_int to compile_time_bounded, or static_bounded.

From: Neal Becker
There is confusion over bounded vs. bounded_int. Maybe rename bounded_int to compile_time_bounded, or static_bounded.
I'm not against changing the name if something better than bounded_int is proposed. However, compile_time_bounded is longish and may be interpreted as checking the bounds at compile time, while static_bounded may suggest that some static members are involved. What do others think?
participants (18)
-
David Abrahams
-
Deane Yang
-
Deane Yang
-
Edward Diener
-
Jeff Garland
-
Jens Weller
-
Joe Gottman
-
John Maddock
-
John Phillips
-
Mathias Gaunard
-
Mika Heiskanen
-
Neal Becker
-
Peter Bartlett
-
Peter Simons
-
Robert Kawulak
-
Stjepan Rajko
-
Thomas Klimpel
-
Thorsten Ottosen