diff --git a/doc/boostbook/automatic.xml b/doc/boostbook/automatic.xml index 33f8015..8fbe1eb 100644 --- a/doc/boostbook/automatic.xml +++ b/doc/boostbook/automatic.xml @@ -29,34 +29,20 @@ The following example illustrates the automatic type being passed as a template parameter for the type - safe<int>. This example is slightly contrived in that - safe<int> has native as it's default - promotion parameter so explictiy using native is not - necessary. + safe<int>. - #include <cassert> -#include <boost/safe_numerics/safe_integer.hpp> -#include <boost/safe_numerics/native.hpp> + #include <boost/safe_numerics/safe_integer.hpp> +#include <boost/safe_numerics/automatic.hpp> + int main(){ using namespace boost::numeric; // use automatic promotion policy where C++ standard arithmetic might lead to incorrect results - using safe_int8 = safe<std::int8_t, automatic>; - try{ - safe_int8 x = 127; - safe_int8 y = 2; - auto z = x + y; // z will be of safe type at least 9 bits long - } - catch(std::exception & e){ - // which can catch here only if - std::cout << e.what() << std::endl; - } + using safe_t = safe<std::int8_t, automatic>; - // When result is an int, C++ promotion rules guarentee that there will be no incorrect result. // In such cases, there is no runtime overhead from using safe types. - safe_int8 x = 127; - safe_int8 y = 2; - safe<int, native> z; // z can now hold the result of the addition of any two 8 bit numbers - z = x + y; // is guarenteed correct without any runtime overhead or interrupt. + safe_t x = 127; + safe_t y = 2; + auto z = x + y; // z is guarenteed correct without any runtime overhead or interrupt. return 0; } diff --git a/doc/boostbook/cpp.xml b/doc/boostbook/cpp.xml index 7469b5b..77f5bcb 100644 --- a/doc/boostbook/cpp.xml +++ b/doc/boostbook/cpp.xml @@ -184,8 +184,6 @@ uint16 get_stopping_distance(LEMPARAMETER velocity){ Note the usage of the compile time trap policy in order to detect at compile time any possible error conditions. As I write this, this is still being refined. Hopefully this will be available by the time you read this. - This example is too long to include in line in this documentation. You can - see the whole file at example.cpp + diff --git a/doc/boostbook/integer_concept.xml b/doc/boostbook/integer_concept.xml index d62423b..a7a6d92 100644 --- a/doc/boostbook/integer_concept.xml +++ b/doc/boostbook/integer_concept.xml @@ -12,7 +12,7 @@ More specifically, a type T is Integer if there exists specialization of std::numeric_limits<T> for which - std::numeric_limits<T>.is_integer is equal to + std::numeric_limits<T>:: is_integer is equal to true. See the documentation for standard library class numeric_limits. The standard library includes such specializations for all the primitive numeric types. Note that this concept is distinct from the @@ -50,7 +50,8 @@ - std::numeric_limits<T>.is_integer + std::numeric_limits<T> + is_integer true diff --git a/doc/boostbook/native.xml b/doc/boostbook/native.xml index a6d225c..c4e0a52 100644 --- a/doc/boostbook/native.xml +++ b/doc/boostbook/native.xml @@ -45,7 +45,8 @@ void int f(safe_int x, safe_int y){ Header #include - <boost/safe_numerics/native.hpp> + <boost/safe_numerics/include/native.hpp> +
diff --git a/doc/boostbook/numeric_concept.xml b/doc/boostbook/numeric_concept.xml index 7a5b07a..ee744e9 100644 --- a/doc/boostbook/numeric_concept.xml +++ b/doc/boostbook/numeric_concept.xml @@ -32,7 +32,7 @@ - T, U + T, U, V A type that is a model of the Numeric diff --git a/doc/boostbook/promotion_policy_concept.xml b/doc/boostbook/promotion_policy_concept.xml index 45f1bad..44b2043 100644 --- a/doc/boostbook/promotion_policy_concept.xml +++ b/doc/boostbook/promotion_policy_concept.xml @@ -2,7 +2,7 @@
- PromotionPolicy<PP> + PromotionPolicy<PP>
Description @@ -64,8 +64,8 @@ auto z = x + ythe type of z will be an EP - A type that full fills the requirements of an - ExceptionPollicy + A type that full fills the requirements of an ExceptionPollicy @@ -171,9 +171,9 @@ auto sz = sx + y; // includes code which traps overflows at runtimeThe type of sz will be safe< type of z >. - This policy is found in the header file #include - <safe_numerics/include/native.hpp> + This policy is documented in Promotion Policies - + native. @@ -196,18 +196,19 @@ safe_unsigned_range<1, 4> a; safe_unsigned_range<2, 4> b; auto c = a + b; // c will be of type safe_unsigned_range<3, 8> and cannot overflow - Type sz will be a SafeNumeric type which is guaranteed to hold - he result of x + y. In this case that will be a long int (or perhaps a - long ong) depending upon the compiler and machine architecture. In - this case, there will be no need for any special checking on the - result and there can be no overflow. + Type sz will be a SafeNumeric type + which is guaranteed to hold he result of x + y. In this case that will + be a long int (or perhaps a long ong) depending upon the compiler and + machine architecture. In this case, there will be no need for any + special checking on the result and there can be no overflow. Type of c will be a signed caracter as that type can be guarenteed to hold the sum so no overflow checking is done. - This policy is found in the header file #include - <safe_numerics/include/automatic.hpp> + This policy is documented in Promotion Policies - + automatic
diff --git a/doc/boostbook/safe.xml b/doc/boostbook/safe.xml index bab9e58..3c00924 100644 --- a/doc/boostbook/safe.xml +++ b/doc/boostbook/safe.xml @@ -2,12 +2,13 @@
- safe<T, PP, EP> + safe<T, PP = boost::numeric::native, EP = + boost::numeric::throw_exception>
Description - A safe<T, PP, EP> can be used anywhere a type T + A safe<T, PP , EP> can be used anywhere a type T can be used. Any expression which uses this type is guarenteed to return an arithmetically correct value or trap in some way.
@@ -55,7 +56,7 @@ PP A type which specifes the result type of an expression - using safe types. + using safe types. @@ -119,7 +120,7 @@ Policy<EP> Default value is boost::throw_exception + linkend="safe_numerics.exception_policy.models.thow_exception">boost::numeric::throw_exception @@ -183,19 +184,20 @@ arithmetic type. Behavior of this program could vary according to the machine architecture in question. - #include <iostream> -#include <stdexcept> // std::runtime_error + #include <exception> +#include <iostream> #include <boost/numeric/safe.hpp> void f(){ + using namespace boost::numeric; safe<int> j; try { safe<int> i; std::cin >> i; // could overflow ! j = i * i; // could overflow } - catch(std::runtime_error & re){ - std::cout << re.what() << std::endl; + catch(std::exception & e){ + std::cout << e.what() << std::endl; } std::cout << j; } @@ -210,17 +212,18 @@ void f(){ exception policy. The following program will emit a compile error at any statement - which might possibly result in incorrect behavior. + which might possibly result in incorrect behavior. This is because there is no way to guarantee that the expression i * i will return an arithmetically correct result. Since we know that the program cannot compile if there is any possibility of arithmetic error, we can dispense with the exception handling used above. - #include <boost/numeric/safe.hpp> -#include <iostream> + #include <iostream> +#include <boost/numeric/safe.hpp> + void f(){ - using safe_int = safe<int, boost::numeric::native, boost::numeric::trap_exception>t; + using safe_int = safe<int, boost::numeric::native, boost::numeric::throw_exception>; safe_int i; std::cin >> i; // could throw exception here !!! safe_int j; diff --git a/doc/boostbook/safe_introduction.xml b/doc/boostbook/safe_introduction.xml index 4f300ae..5bd5327 100644 --- a/doc/boostbook/safe_introduction.xml +++ b/doc/boostbook/safe_introduction.xml @@ -74,7 +74,7 @@ #include <boost/safe_numeric/safe_integer.hpp> -int f(safe<int> x, safe<int> y){ +safe<int> f(safe<int> x, safe<int> y){ return x + y; // throw exception if correct result cannot be returned } @@ -151,12 +151,12 @@ int f(safe<int> x, safe<int> y){ - Trap any possible arithmetic errors at compile time + Trap some arithmetic errors at compile time - Ranged integer types to permit enforcement of other program - requirements. + Enforce of other program requirements using ranged integer + types. @@ -169,7 +169,7 @@ int f(safe<int> x, safe<int> y){ Requirements This library is composed entirely of C++ Headers. I requires a - compiler compatible with the C++11 standard. + compiler compatible with the C++14 standard. The following Boost Libraries must be installed in order to use this library @@ -183,10 +183,6 @@ int f(safe<int> x, safe<int> y){ integer - - limits - - config @@ -194,14 +190,6 @@ int f(safe<int> x, safe<int> y){ concept checking - - - type traits - - - - integer traits -
diff --git a/doc/boostbook/safe_numeric_concept.xml b/doc/boostbook/safe_numeric_concept.xml index b080cf9..6c08858 100644 --- a/doc/boostbook/safe_numeric_concept.xml +++ b/doc/boostbook/safe_numeric_concept.xml @@ -207,6 +207,13 @@ + + The key fact documented in the above table is that the result of any + binary operation where one or both of the operands is a SafeNumeric type + is also a SafeNumeric type. Also note that all the expressions in the + above table are constexpr expressions. So that if they are + invoked with constant values, the result will be available a compile + time.
diff --git a/doc/boostbook/safe_numerics.xml b/doc/boostbook/safe_numerics.xml index 0dc3649..fdf20ef 100644 --- a/doc/boostbook/safe_numerics.xml +++ b/doc/boostbook/safe_numerics.xml @@ -39,9 +39,9 @@ This library really an re-implementation the facilities provided by David LeBlanc's SafeInt - Library using Boost. I found - this library very well done in every way. My main usage was to run unit - tests for my embedded systems projects on my PC. Still, I had a few + Library using Boost and C++14. + I found this library very well done in every way. My main usage was to run + unit tests for my embedded systems projects on my PC. Still, I had a few issues. @@ -76,7 +76,7 @@ This version addresses these issues. It exploits Boost facilities such as template metaprogramming to reduce the number of lines of source code to - approximately 1500. It exploits the Boost Preprocessor Library to generate + approximately 4400. It exploits the Boost Preprocessor Library to generate exhaustive tests. All concepts, types and functions documented are declared in the @@ -157,7 +157,7 @@ Is this really necessary? If I'm writing the program with the requisite care and competence, problems noted in the introduction will never arise. Should they arise, they should be fixed "at the - source" and with a "bandaid" to cover up bad practice. + source" and not with a "bandaid" to cover up bad practice. @@ -231,9 +231,9 @@ - It looks like it presumes two's complement arithmetic at the - hardware level. So this library is not portable - correct? What - about other hardware architectures? + It looks like the implementation presumes two's complement + arithmetic at the hardware level. So this library is not portable - + correct? What about other hardware architectures? diff --git a/doc/boostbook/safe_range.xml b/doc/boostbook/safe_range.xml index 4038c2f..9c7acd5 100644 --- a/doc/boostbook/safe_range.xml +++ b/doc/boostbook/safe_range.xml @@ -139,7 +139,7 @@ Policy<EP> Default value is boost::throw_exception + linkend="safe_numerics.exception_policy.models.thow_exception">boost::numeric::throw_exception @@ -177,7 +177,9 @@ #include <safe/numeric/safe_range.hpp> -void f(boost::numeric::safe_unsigned_range<7, 24> i){ +void f(){ + using namespace boost::numeric; + safe_unsigned_range<7, 24> i // since the range is included in [0,255], the underlying type of i // will be an unsigned char. i = 0; // throws out_of_range exception @@ -186,10 +188,12 @@ void f(boost::numeric::safe_unsigned_range<7, 24> i){ i = -1; // throws out_of_range exception std::uint8_t j = 4; auto k = i + j; - // These types use default promotion policy so standard C++ type promotion rules - // will be used in evaluating expressions. C++ type promotion rules mandate - // that the result of k = i + j when both are unsigned char types, will be another - // unsigned char type. + + // the range of i is [7, 24] and the range of j is [0,255] + // if either or both types are safe types, the result is a safe type + // determined by promotion policy. With the default native promotion policy + // k will be safe<unsigned int> + static_assert(std::is_same<decltype(k), safe<unsigned int>); }
diff --git a/doc/boostbook/tutorial.xml b/doc/boostbook/tutorial.xml index 52b3dca..013743d 100644 --- a/doc/boostbook/tutorial.xml +++ b/doc/boostbook/tutorial.xml @@ -5,7 +5,7 @@ Tutorial and Motivating Examples
- Arithmetic operations can yield incorrect results. + Arithmetic Operations Can Yield Incorrect Results. When some operation results in a result which exceeds the capacity of a data variable to hold it, the result is undefined. This is called @@ -29,36 +29,30 @@
- Undetected overflow + Undetected Overflow A variation of the above is when a value is incremented/decremented beyond it's domain. This is a common problem with for loops. -
-
- Undetected underflow - - A variation of the above is when a value is incremented/decremented - beyond it's domain. This is a common problem with for loops. - - + When variables of unsigned integer type are decremented below zero, + they "roll over" to the highest possible unsigned version of that integer + type. This is a common problem which is generally never detected.
- Implicit conversions change data values + Implicit Conversions Change Data Values - A simple assignment or arithmetic expression will generally convert - all the terms to the same type. Sometimes this can silently change values. - For example, when a signed data variable contains a negative type, - assigning to a unsigned type will be permitted by any C/C++ compiler but - will be treated as large unsigned value. Most modern compilers will emit a - compile time warning when this conversion is performed. The user may then - decide to change some data types or apply a static_cast. This - is less than satisfactory for two reasons: + A simple assignment or arithmetic expression will convert all the + terms to the same type. Sometimes this can silently change values. For + example, when a signed data variable contains a negative type, assigning + to a unsigned type will be permitted by any C/C++ compiler but will be + treated as large unsigned value. Most modern compilers will emit a compile + time warning when this conversion is performed. The user may then decide + to change some data types or apply a static_cast. This is + less than satisfactory for two reasons: @@ -66,14 +60,10 @@ unsigned. - - Littering one's program with static_cast - makes it more difficult to read. - - We may believe that our signed type will never contain a - negative value. If we ignore the any compiler warnings or use a + negative value. static_cast changes the data type - not + the data value. If we ignore the any compiler warnings or use a static_cast to suppress them, we'll fail to detect a program error when it is committed. This is aways a risk with casts. @@ -87,7 +77,7 @@
- Array index value can exceed array limits + Array Index Value Can Exceed Array Limits Using an intrinsic C++ array, it's very easy to exceed array limits. This can fail to be detected when it occurs and create bugs which are hard @@ -95,34 +85,47 @@ would be to use safe_unsigned_range; + parse="text" xmlns:xi="http://www.w3.org/2001/XInclude"/>Collections + like standard arrays, vectors do array index checking in some function + calls and not in others so this may not be the best example. However it + does illustrate the usage of safe_range<T> for + assigning legal range to variables. This will guarentee that under no + circumstances will the variable contain a value outside of the specified + range.
- Checking of initialization values can be easily overlooked + Checking of Input Values Can Be Easily Overlooked It's way too easy to overlook the checking of parameters received from outside the current program.Without safe integer, one will have to insert new code every time an integer - variable is retrieved. This is a tedious and error prone procedure. + variable is retrieved. This is a tedious and error prone procedure. Here + we have used program input. But in fact this problem can occur with any + externally produced input.
- Parameter checking is too expensive + Programming by Contract is Too Slow Programming by Contract is a highly regarded technique. There has been much written about it has been proposed as an addition to the C++ language GarciaCrowl & - Ottosen. It (mostly depends upon runtime checking of parameter - values upon entry to every function and thus slows the program down. This - undermines the main motivation for using C++ in the first place! One - popular scheme for addressing this issue is to enable parameter checking - only during debugging and testing. This defeats the guarentee of - correctness which we are seeking here! This library has facilities which, - in many cases, can check guarentee parameter requirements with no runtime - overhead. Consider the following example: + Ottosen. It (mostly) depends upon runtime checking of parameter + and object values upon entry to and exit from every function. This can + slow the program down considerably which in turn undermines the main + motivation for using C++ in the first place! One popular scheme for + addressing this issue is to enable parameter checking only during + debugging and testing which defeats the guarentee of correctness which we + are seeking here! Programming by Contract will never be accepted by + programmers as long as it is associated with significant additional + runtime cost. + + The Safe Numerics Library has facilities which, in many cases, can + check guarentee parameter requirements with little or no runtime overhead. + Consider the following example: @@ -137,7 +140,7 @@
- Eliminate runtime cost + Eliminate Runtime Cost Our system works by checking arithmetic operations whenever they could result in an erroneous result. The C++ standard describes how binary @@ -163,12 +166,18 @@ So the result of the sum of two integer types will result in another integer type. If the values are large, they will exceed the size that the resulting integer type can hold. This is what we call "overflow". Standard - C++ just truncates the result to fit into the result type - which makes - the result arithmetically incorrect. Up until now, we've focused on - detecting when this happens and invoking an interrupt or other kind of - error handler. But now we look at another option. Using the "automatic" - type promotion policy, we can change the rules of C++ arithmetic for safe - types to something like the following: + C/C++ just truncates the result to fit into the result type - which makes + the result arithmetically incorrect. This behavior is consistent with the + default "native" type + promotion policy. Up until now, we've focused on detecting when + this happens and invoking an interrupt or other kind of error handler. + + + But now we look at another option. Using the "automatic" type + promotion policy, we can change the rules of C++ arithmetic for + safe types to something like the following: @@ -180,12 +189,12 @@ For any binary operation on these types, we can calculate the - interval of the result. + interval of the result at compile time. - From this we can determine a new safe type which can be - guarenteed to hold the result. + From this interval we can determine a new safe type which can + be guarenteed to hold the result. @@ -195,13 +204,13 @@ The only error checking we need to do is when safe values are - initialized, but this we would have to do in any case. So we've - eliminated arithmetically incorrect results while incurring zero - runtime overhead for error checking. + initialized or assigned, but this we would have to do in any case. + So we've eliminated arithmetically incorrect results while incurring + zero runtime overhead for error checking. - In sort, given a binary operation, we promote the constituent types + In short, given a binary operation, we promote the constituent types to a larger result type which can't overflow. This is a fundamental deparature from the C++ Standard behavior. @@ -217,6 +226,29 @@ works. + parse="text" xmlns:xi="http://www.w3.org/2001/XInclude"/>The + above program produces the following output: + + example 8: eliminate runtime overhead +x<signed char>[-24,82] = 1 +y<signed char>[-24,82] = 2 +(x + y)<short>[-48,164] = 3 +(x - y)<signed char>[-106,106] = -1Variables x and y + are stored as 8 bit signed integers with range specied as -24 to 82. The + result of x + y could be any value in the range -48 to 164. Since this + result can't be stored in an 8 bit signed integer, a 16 bit signed integer + is allocated. The result x - y could range from -106 to 106 so will fit in + an 8 bit signed integer is allocated. Binary operations with safe numeric + using automatic type promotion will produce other safe numeric types with + template parameters appropriate to hold the result. The resultant safe + types may have smaller or larger ranges than the parameters of the binary + operation. + + We've used simple expressions in this illustration. But since binary + operations on safe types result in other safe types, expressions can be + made arbitrarily elaborate - just as they can be with intrinsic integer + types. That is, safe integer types are drop in replacements for intrinsic + integer types. We are guarenteed never to produce an incorrect result + regardless of how elaborate the expression might be.
diff --git a/examples/example1.cpp b/examples/example1.cpp index f122b03..86d6b6b 100644 --- a/examples/example1.cpp +++ b/examples/example1.cpp @@ -4,30 +4,25 @@ #include "../include/safe_integer.hpp" -void detected_msg(bool detected){ - std::cout << (detected ? "error detected!" : "error NOT detected! ") << std::endl; -} - int main(int argc, const char * argv[]){ std::cout << "example 1:"; std::cout << "undetected erroneous expression evaluation" << std::endl; std::cout << "Not using safe numerics" << std::endl; + // problem: arithmetic operations can yield incorrect results. try{ char x = 127; char y = 2; char z; // this produces an invalid result ! z = x + y; - // it is the wrong result !!! - assert(z != 129); // but assert fails to detect it since C++ implicitly // converts variables to int before evaluating he expression! assert(z != x + y); std::cout << static_cast(z) << " != " << x + y << std::endl; - detected_msg(false); + std::cout << "error NOT detected!" << std::endl; } catch(std::exception){ - assert(false); // never arrive here + std::cout << "error detected!" << std::endl; } // solution: replace char with safe std::cout << "Using safe numerics" << std::endl; @@ -38,12 +33,10 @@ int main(int argc, const char * argv[]){ safe z; // rather than producing and invalid result an exception is thrown z = x + y; - assert(false); // never arrive here } catch(std::exception & e){ // which can catch here std::cout << e.what() << std::endl; - detected_msg(true); } return 0; } diff --git a/examples/example2.cpp b/examples/example2.cpp index 7895a5d..1024717 100644 --- a/examples/example2.cpp +++ b/examples/example2.cpp @@ -4,24 +4,23 @@ #include "../include/safe_integer.hpp" -void detected_msg(bool detected){ - std::cout << (detected ? "error detected!" : "error NOT detected! ") << std::endl; -} - int main(int argc, const char * argv[]){ std::cout << "example 2:"; std::cout << "undetected overflow in data type" << std::endl; + // problem: undetected overflow + std::cout << "Not using safe numerics" << std::endl; try{ int x = INT_MAX; // the following silently produces an incorrect result ++x; std::cout << x << " != " << INT_MAX << " + 1" << std::endl; - detected_msg(false); + std::cout << "error NOT detected!" << std::endl; } catch(std::exception){ - assert(false); // never arrive here + std::cout << "error detected!" << std::endl; } // solution: replace int with safe + std::cout << "Using safe numerics" << std::endl; try{ using namespace boost::numeric; safe x = INT_MAX; @@ -31,7 +30,7 @@ int main(int argc, const char * argv[]){ } catch(std::exception & e){ std::cout << e.what() << std::endl; - detected_msg(true); + std::cout << "error detected!" << std::endl; } return 0; } diff --git a/examples/example3.cpp b/examples/example3.cpp index c123fea..687c676 100644 --- a/examples/example3.cpp +++ b/examples/example3.cpp @@ -4,29 +4,27 @@ #include "../include/safe_integer.hpp" -void detected_msg(bool detected){ - std::cout << (detected ? "error detected!" : "error NOT detected! ") << std::endl; -} - int main(int argc, const char * argv[]){ std::cout << "example 3:"; std::cout << "undetected underflow in data type" << std::endl; std::cout << "Not using safe numerics" << std::endl; + // problem: decrement can yield incorrect result try{ unsigned int x = 0; // the following silently produces an incorrect result --x; - // because C/C++ implicitly converts mis-matched arguments to int - // suggests that the operation is correct - assert(x == -1); - // even though it's not !!! - - // so the error is not detected! std::cout << x << " != " << -1 << std::endl; - detected_msg(false); + + // when comparing int and unsigned int, C++ converts + // the int to unsigned int so the following assertion + // fails to detect the above error! + assert(x == -1); + + std::cout << "error NOT detected!" << std::endl; } catch(std::exception){ - assert(false); // never arrive here + // never arrive here + std::cout << "error detected!" << std::endl; } // solution: replace unsigned int with safe std::cout << "Using safe numerics" << std::endl; @@ -39,7 +37,7 @@ int main(int argc, const char * argv[]){ } catch(std::exception & e){ std::cout << e.what() << std::endl; - detected_msg(true); + std::cout << "error detected!" << std::endl; } return 0; } diff --git a/examples/example4.cpp b/examples/example4.cpp index 46f7241..5cd8efb 100644 --- a/examples/example4.cpp +++ b/examples/example4.cpp @@ -1,25 +1,22 @@ -#include #include #include #include "../include/safe_integer.hpp" -void detected_msg(bool detected){ - std::cout << (detected ? "error detected!" : "error NOT detected! ") << std::endl; -} - int main(int argc, const char * argv[]){ std::cout << "example 4: "; std::cout << "implicit conversions change data values" << std::endl; std::cout << "Not using safe numerics" << std::endl; + // problem: implicit conversions change data values try{ int x = -1000; // the following silently produces an incorrect result char y = x; - detected_msg(false); + std::cout << x << " != " << (int)y << std::endl; + std::cout << "error NOT detected!" << std::endl; } catch(std::exception){ - assert(false); // never arrive here + std::cout << "error detected!" << std::endl; // never arrive here } // solution: replace int with safe and char with safe std::cout << "Using safe numerics" << std::endl; @@ -28,14 +25,13 @@ int main(int argc, const char * argv[]){ safe x = -1000; // throws exception when conversion change data value safe y1(x); - safe y3 = x; - safe y = {x}; - y = x; - assert(false); // never arrive here + safe y2 = x; + safe y3 = {x}; + std::cout << "error NOT detected!" << std::endl; } catch(std::exception & e){ std::cout << e.what() << std::endl; - detected_msg(true); + std::cout << "error detected!" << std::endl; } return 0; } diff --git a/examples/example5.cpp b/examples/example5.cpp index 2abe8ef..be19467 100644 --- a/examples/example5.cpp +++ b/examples/example5.cpp @@ -1,4 +1,3 @@ -#include #include #include @@ -13,28 +12,28 @@ int main(int argc, const char * argv[]){ std::cout << "example 5: "; std::cout << "array index values can exceed array bounds" << std::endl; std::cout << "Not using safe numerics" << std::endl; - int i_array[37]; + std::array i_array; // unsigned int i_index = 43; // the following corrupts memory. // This may or may not be detected at run time. // i_array[i_index] = 84; // comment this out so it can be tested! - detected_msg(false); + std::cout << "error NOT detected!" << std::endl; // solution: replace unsigned array index with safe_unsigned_range std::cout << "Using safe numerics" << std::endl; try{ using namespace boost::numeric; - safe_unsigned_range<0, sizeof(i_array)/sizeof(int) - 1> i_index; + using i_index_t = safe_unsigned_range<0, i_array.size() - 1>; + i_index_t i_index; i_index = 36; // this works fine i_array[i_index] = 84; - i_index = 37; // throw exception here! - i_array[i_index] = 84; // so we never arrive here - assert(false); + i_index = 43; // throw exception here! + std::cout << "error NOT detected!" << std::endl; // so we never arrive here } catch(std::exception & e){ std::cout << e.what() << std::endl; - detected_msg(true); + std::cout << "error detected!" << std::endl; } return 0; } diff --git a/examples/example6.cpp b/examples/example6.cpp index cb65e7b..4ed945f 100644 --- a/examples/example6.cpp +++ b/examples/example6.cpp @@ -1,14 +1,9 @@ -#include #include #include #include #include "../include/safe_integer.hpp" -void detected_msg(bool detected){ - std::cout << (detected ? "error detected!" : "error NOT detected! ") << std::endl; -} - int main(int argc, const char * argv[]){ // problem: checking of externally produced value can be overlooked std::cout << "example 6: "; @@ -17,25 +12,30 @@ int main(int argc, const char * argv[]){ std::istringstream is("12317289372189 1231287389217389217893"); - int x, y, z; - is >> x >> y; // get integer values from the user - z = x + y; - std::cout << z << std::endl; // display sum of the values - detected_msg(false); - + try{ + int x, y; + is >> x >> y; // get integer values from the user + std::cout << x << ' ' << y << std::endl; + std::cout << "error NOT detected!" << std::endl; + } + catch(std::exception){ + std::cout << "error detected!" << std::endl; + } + // solution: asign externally retrieved values to safe equivalents std::cout << "Using safe numerics" << std::endl; { using namespace boost::numeric; - safe x, y, z; + safe x, y; is.seekg(0); try{ is >> x >> y; // get integer values from the user - detected_msg(false); + std::cout << x << ' ' << y << std::endl; + std::cout << "error NOT detected!" << std::endl; } - catch(std::exception e){ + catch(std::exception & e){ std::cout << e.what() << std::endl; - detected_msg(true); + std::cout << "error detected!" << std::endl; } } return 0; diff --git a/examples/example7.cpp b/examples/example7.cpp index 73bfe70..fcde599 100644 --- a/examples/example7.cpp +++ b/examples/example7.cpp @@ -5,6 +5,7 @@ #include "../include/safe_range.hpp" +// NOT using safe numerics - enforce program contract explicitly // return total number of minutes unsigned int convert( const unsigned int & hours, @@ -21,6 +22,7 @@ unsigned int convert( return hours * 60 + minutes; } +// Use safe numeric to enforce program contract automatically // define convient typenames for hours and minutes hh:mm using hours_t = boost::numeric::safe_unsigned_range<0, 23>; using minutes_t = boost::numeric::safe_unsigned_range<0, 59>; @@ -86,26 +88,27 @@ int main(int argc, const char * argv[]){ try { // the following will never throw as the values meet requirements. - hours_t hours(10); - minutes_t minutes(17); + const hours_t hours(10); + const minutes_t minutes(17); + + // note zero runtime overhead once values are constructed // the following will never throw because it cannot be called with // invalid parameters - safe_convert(hours, minutes); + safe_convert(hours, minutes); // zero runtime overhead // since safe types can be converted to their underlying unsafe types // we can still call an unsafe function with safe types - convert(hours, minutes); + convert(hours, minutes); // zero (almost) runtime overhead // since unsafe types can be implicitly converted to corresponding // safe types we can just pass the unsafe types. checkin will occur // when the safe type is constructed. - safe_convert(10, 17); + safe_convert(10, 17); // runtime cost in creating parameters - // note zero runtime overhead once values are constructed } catch(std::exception e){ - assert(false); // can never arrive here !!! + std::cout << "error detected!" << std::endl; } return 0; diff --git a/examples/example8.cpp b/examples/example8.cpp index 6a00e6b..d31ac82 100644 --- a/examples/example8.cpp +++ b/examples/example8.cpp @@ -3,10 +3,46 @@ #include #include #include +#include #include "../include/safe_range.hpp" #include "../include/automatic.hpp" +// create an output manipulator which prints variable type and limits +// as well as value +template +struct formatted_impl { + const T & m_t; + formatted_impl(const T & t) : + m_t(t) + {} + template + friend std::basic_ostream & + operator<<( + std::basic_ostream & os, + const formatted_impl & f + ){ + int status; + return os + << "<" + << abi::__cxa_demangle( + typeid(boost::numeric::base_value(m_t)).name(),0,0,&status + ) + << ">[" + << std::numeric_limits::min() << "," + << std::numeric_limits::max() << "] = " + << f.m_t; + } +}; + +template +auto formatted(const T & t){ + return formatted_impl(t); +} + +// create a type for holding small integers which implement automatic +// type promotion to larger types to guarentee correct results with +// zero runtime overhead ! template < std::intmax_t Min, std::intmax_t Max @@ -17,64 +53,21 @@ using safe_t = boost::numeric::safe_signed_range< boost::numeric::automatic, boost::numeric::throw_exception >; - -// I can't figure out how to overload os << for safe_t -// we use the following workaround there - -// wrap a safe_t in a "formatted" wrapper -template -struct formatted { - using wrapped_type = T; - const T & m_t; - formatted(const T & t) : - m_t(t) - {} -}; - -template -auto make_formatted(const T & t){ - return formatted(t); -} - -// now (fully) specialize output of safe types wrapped in formatted -template< - class T, - T Min, - T Max, - class P, // promotion polic - class E // exception policy -> -std::ostream & operator<<( - std::ostream & os, - const formatted> & f -){ - using safe_type = typename formatted >::wrapped_type; - return os - << "[" - << std::numeric_limits::min() << "," - << std::numeric_limits::max() << "] = " - << f.m_t; -} +using small_integer_t = safe_t<-24, 82>; int main(int argc, const char * argv[]){ // problem: checking of externally produced value can be overlooked std::cout << "example 8: "; - std::cout << "eliminate runtime overhead" - << std::endl; + std::cout << "eliminate runtime overhead" << std::endl; try{ - int status; - const safe_t<-64, 63> x(1); - std::cout << abi::__cxa_demangle(typeid(x).name(),0,0,&status) << '\n'; - - std::cout << "x" << make_formatted(x) << std::endl; - safe_t<-64, 63> y; - y = 2; - std::cout << "y" << make_formatted(y) << std::endl; - auto z = x + y; - std::cout << "(x + y)" << make_formatted(z) << std::endl; - - std::cout << "(x - y)" << make_formatted(x - y) << std::endl; + const small_integer_t x(1); + std::cout << "x" << formatted(x) << std::endl; + small_integer_t y = 2; + std::cout << "y" << formatted(y) << std::endl; + auto z = x + y; // zero runtime overhead ! + std::cout << "(x + y)" << formatted(z) << std::endl; + std::cout << "(x - y)" << formatted(x - y) << std::endl; } catch(std::exception e){ // none of the above should trap. Mark failure if they do diff --git a/include/checked_result.hpp b/include/checked_result.hpp index c4ed903..e830cb4 100644 --- a/include/checked_result.hpp +++ b/include/checked_result.hpp @@ -9,13 +9,9 @@ #ifndef BOOST_NUMERIC_CHECKED_RESULT #define BOOST_NUMERIC_CHECKED_RESULT -#include //nullptr_t -#include #include #include "safe_common.hpp" // SAFE_NUMERIC_CONSTEXPR -#include "exception_policies.hpp" -#include "concept/exception_policy.hpp" #include "safe_compare.hpp" namespace boost { @@ -33,31 +29,27 @@ enum class exception_type { template struct checked_result { -// poor man's variant which supports SAFE_NUMERIC_CONSTEXPR exception_type m_e; union { R m_r; char const * m_msg; }; // constructors - // breaks add/subtract etc.!!! - // perhaps because defining a copy constructure suppresses implicite move? + // can't select constructor based on the current status of another + // checked_result object. /* SAFE_NUMERIC_CONSTEXPR checked_result(const checked_result & r) : m_e(r.m_e) { - (m_e == exception_type::no_exception) ? + (no_exception()) ? (m_r = r.m_r), 0 : (m_msg = r.m_msg), 0 ; } - // don't permit construction without initial value; - SAFE_NUMERIC_CONSTEXPR explicit checked_result() : - m_e(exception_type::uninitialized), - m_r(0) - {} */ + // don't permit construction without initial value; + checked_result() = delete; SAFE_NUMERIC_CONSTEXPR /*explicit*/ checked_result(const R & r) : m_e(exception_type::no_exception), @@ -71,18 +63,6 @@ struct checked_result { m_e(e), m_msg(msg) {} - #if 0 - template - SAFE_NUMERIC_CONSTEXPR /*explicit*/ checked_result(const checked_result & t) : - m_e(t.m_e) - { - if(t.no_exception()) - m_r = t.m_r; - else{ - m_msg = t.m_msg; - } - } - #endif // accesors SAFE_NUMERIC_CONSTEXPR operator R() const { @@ -90,11 +70,6 @@ struct checked_result { return m_r; } - /* - SAFE_NUMERIC_CONSTEXPR operator exception_type() const { - return m_e; - } - */ SAFE_NUMERIC_CONSTEXPR operator const char *() const { // assert(exception_type::no_exception != m_e); return m_msg; @@ -142,32 +117,6 @@ struct checked_result { return m_e == exception_type::no_exception; } -/* - template - void - dispatch() const { - switch(m_e){ - case exception_type::overflow_error: - EP::overflow_error(m_msg); - break; - case exception_type::underflow_error: - EP::underflow_error(m_msg); - break; - case exception_type::range_error: - EP::range_error(m_msg); - break; - case exception_type::domain_error: - EP::domain_error(m_msg); - break; - case exception_type::no_exception: - break; - default: - break; - } - } -}; -*/ - template SAFE_NUMERIC_CONSTEXPR void dispatch() const { @@ -194,68 +143,6 @@ struct checked_result { } }; -/* -template -SAFE_NUMERIC_CONSTEXPR inline const checked_result min( - const checked_result & t, - const checked_result & u -){ - return - (t.m_e == exception_type::no_exception - && u.m_e == exception_type::no_exception) ? - t.m_r < u.m_r ? - t - : - u - : - checked_result( - exception_type::range_error, - "Can't compare values without values" - ) - ; -} - -template -SAFE_NUMERIC_CONSTEXPR inline const checked_result max( - const checked_result & t, - const checked_result & u -){ - return - (t.m_e == exception_type::no_exception - && u.m_e == exception_type::no_exception) ? - t.m_r < u.m_r ? - u - : - t - : - checked_result( - exception_type::range_error, - "Can't compare values without values" - ) - ; -} - -template -SAFE_NUMERIC_CONSTEXPR inline const bool operator<( - const checked_result & t, - const checked_result & u -){ - return - (t.m_e == exception_type::no_exception - && u.m_e == exception_type::no_exception) ? - t.m_r < u.m_r ? - t - : - u - : - checked_result( - exception_type::range_error, - "Can't compare values without values" - ) - ; -} -*/ - // C++ does not (yet) permit constexpr lambdas. So create some // constexpr predicates to be used by constexpr algorthms. template @@ -266,7 +153,6 @@ constexpr bool no_exception(const checked_result & cr){ } // numeric } // boost -//#include #include #include diff --git a/include/exception_policies.hpp b/include/exception_policies.hpp index 253ad7c..d499391 100644 --- a/include/exception_policies.hpp +++ b/include/exception_policies.hpp @@ -25,10 +25,10 @@ namespace numeric { // this would emulate the normal C/C++ behavior of permitting overflows // and the like. struct ignore_exception { - static void overflow_error(const char * message) {} - static void underflow_error(const char * message) {} - static void range_error(const char * message) {} - static void domain_error(const char * message) {} + constexpr static void overflow_error(const char * message) {} + constexpr static void underflow_error(const char * message) {} + constexpr static void range_error(const char * message) {} + constexpr static void domain_error(const char * message) {} }; // example - if you want to specify specific behavior for particular exception @@ -58,16 +58,16 @@ struct no_exception_support { // If an exceptional condition is detected at runtime throw the exception. struct throw_exception { - static void overflow_error(const char * message) { + constexpr static void overflow_error(const char * message) { throw std::overflow_error(message); } - static void underflow_error(const char * message) { + constexpr static void underflow_error(const char * message) { throw std::underflow_error(message); } - static void range_error(const char * message) { + constexpr static void range_error(const char * message) { throw std::domain_error(message); } - static void domain_error(const char * message) { + constexpr static void domain_error(const char * message) { throw std::domain_error(message); } };