void f(const doubleSubMatrix& M) {
if (seldom_true()) {
Length k = M.length3();// error detected at compile-time
}
...
}
will be detected at compile-time but this error:
void g(const Array<double, 2>& M) {
if (seldom_true()) {
int k = M.extent(3);// error cannot be detected
// until executed at run-time
}
...
}
cannot be detected until member function `extent(int)' is executed
at run-time. If the application programmer fails to ensure that
the conditional evaluates to true sometime during testing,
the error may not be discovered until after the black box recorder
is recovered. Member function `extent(int)' may be useful
for those rare situations where the argument is variable
but it only exposes the application to unnecessary risk
in the much more common situation where the argument is constant.
In general, detection of programming errors at compile-time
is facilitated by communicating as much information as practical
to the compiler through function and type names. But
it isn't always practical to detect programming errors at compile time.
Some information, such as the dimensions of subtensors,
might not be available until run-time.
The SVMT class library standard does not specify any run-time checking
for programming errors because it only hinders performance once all
of the programming errors have been detected and eliminated.
But SVMT class library developers are expected to provide application
programmers with the means to detect programming errors at run-time.
The SVMT classes are designed to allow the application programmer
to compile and/or link in optional error checking code before testing
the application program. This code is transparent to the application
much like the code included by the compiler for a symbolic debugger.
Since checking for programming errors is transparent to the application,
no standard is required and implementation details are left up to
the library developer.
The SVMT class library standard does not specify IEEE
or any other floating-point arithmetic.
It does not specify any standard for handling floating-point exceptions.
The behavior of SVMT functions might not even be consistent
with the corresponding functions in the standard math library.
This means that the portable application programmer must include
code to detect and handle floating-point exceptions explicitly.
For example, since the square root of a negative real number is undefined,
the portable application programmer is obliged to write something like:
if (0 <= v) {
u = sqrt(v);
}
else {
/* handle the error */
}
if there is reason to believe that some element of subvector v
might be negative. Otherwise, the sqrt(v) function would issue
a warning message to the application programmer in debug mode
if any element of vector v is negative even if an exception
handler is installed for the square root of a negative number.
Besides math errors, the library should check for the following errors
in debug mode:
1. subscript 0 <= j < n out of bounds. For example:
inline
doubleSubScalar
doubleSubVector::operator [](Offset j) {
#ifdef SVMT_DEBUG_MODE
svmt_check_vector_index_range(
"doubleSubVector::operator [](Offset)", j, length());
#endif // SVMT_DEBUG_MODE
return doubleSubScalar(handle(), offset() + (Stride)j*stride()); }
2. slice references elements outside of a subtensor. For example:
inline
doubleSubVector doubleSubVector::sub(Offset j, Length n, Stride s) {
#ifdef SVMT_DEBUG_MODE
svmt_check_vector_containment(
"doubleSubVector::sub(Offset, Length, Stride)", j, n, s, length());
#endif // SVMT_DEBUG_MODE
return doubleSubVector(handle(),
offset() + (Stride)j*stride(), n, s*stride()); }
3. subtensor reference elements outside of the 1D array. In debug mode,
the size of the 1D array is stored along with the 1D array so that
it is possible to determine whether or not every element of the subtensor
is actually contained within the 1D array.
4. uninitialized right hand side. In debug mode, the SVMT library developer
might include code to initialize uninitialized Vector, Matrix and Tensor
objects with an absurd value (such as a signaling NaN) which affect results
in a way that the application programmer is likely to notice. For example:
inline doubleVector::doubleVector(Length n):
doubleSubVector(doubleHandle(new double[n]),
(Offset)0, n, (Stride)1) {
#ifdef SVMT_DEBUG_MODE
doubleSubVector::operator =(NAN);
#endif // SVMT_DEBUG_MODE
}
5. non-conformant subtensors. Both operands of all element by element
vector-vector, matrix-matrix and tensor-tensor binary operations
(including assignment operations) must be the same size.
The rows of both operands of a dot product must be the same length.
In debug mode, the SVMT library developer would include code to check
for conformance. For example:
doubleSubVector&
doubleSubVector::operator =(const doubleSubVector& w) {
#ifdef SVMT_DEBUG_MODE
svmt_check_vector_conformance(
"doubleSubVector::operator =(const doubleSubVector&)",
length(), w.length());
#endif // SVMT_DEBUG_MODE
doubleHandle& h = (doubleHandle&)handle();
Offset o = offset();
Length n = length();
Stride s = stride();
const
doubleHandle& hw = w.handle();
Offset ow = w.offset();
Stride sw = w.stride();
for (Offset j = 0; j < n; j++) {
h.put(o, hw.get(ow));
o += s; ow += sw;
}
return *this;
}
6. unsafe assignment operations. Any assignment operation
which might overwrite elements of any operand on the right-hand side
before they are used is an unsafe assignment operation. For example:
v = v + u; // is safe but
v = v.reverse() + u; // is not safe.
The application programmer might be obliged to write something like
v = doubleVector(v.reverse()) + u;
in order to ensure that the application will be portable.
In debug mode, the library developer is expected to detect whether or not
the left-hand side overlaps itself or any argument on the right-hand side
but it might be very expensive to decide whether some elements actually
coincide or are simply interleaved.
7. destroying the 1D array before all references to it are destroyed.
The application programmer has almost certainly made a mistake
if a tensor object is destroyed before all of the subtensors
which reference the same underlying 1D array of numbers. In debug mode,
a reference counter might be stored along with the 1D array of numbers
so that the destructor could issue a warning message
to the application programmer if the count is not reduced to zero.
8. memory leaks. The application programmer may forget to delete
a subtensor object allocated from the free store with new. In debug mode,
the library might maintain a [global] counter for each type of subtensor
which is incremented by the constructor when the subtensor is created,
decremented when a subtensor is destroyed and examined by the debugger
at any break point so that the application programmer can pinpoint
an SVMT related memory leak easily.
9. catastrophic loss of significance. Some operations like downcasting
and the dot product can result in a total loss of significance which
the library should report to the application programmer in debug mode.
Bob Tisdale <edwin2@gte.net>