2.9. Error Handling

Viskores contains several mechanisms for checking and reporting error conditions.

2.9.1. Runtime Error Exceptions

Viskores uses exceptions to report errors. All exceptions thrown by Viskores will be a subclass of viskores::cont::Error. For simple error reporting, it is possible to simply catch a viskores::cont::Error and report the error message string reported by the viskores::cont::Error::GetMessage() method.

Example 2.62 Simple error reporting.
 1int main(int argc, char** argv)
 2{
 3  try
 4  {
 5    // Do something cool with Viskores
 6    // ...
 7  }
 8  catch (const viskores::cont::Error& error)
 9  {
10    std::cout << error.GetMessage() << std::endl;
11    return 1;
12  }
13  return 0;
14}
class Error : public std::exception

The superclass of all exceptions thrown by any Viskores function or method.

Subclassed by viskores::cont::ErrorBadAllocation, viskores::cont::ErrorBadDevice, viskores::cont::ErrorBadType, viskores::cont::ErrorBadValue, viskores::cont::ErrorExecution, viskores::cont::ErrorFilterExecution, viskores::cont::ErrorInternal, viskores::cont::ErrorUserAbort, viskores::cont::cuda::ErrorCuda, viskores::io::ErrorIO

Public Functions

inline const std::string &GetMessage() const

Returns a message describing what caused the error.

inline const std::string &GetStackTrace() const

Provides a stack trace to the location where this error was thrown.

inline const char *what() const noexcept override

Returns the message for the error and the stack trace for it.

This method is provided for std::exception compatibility.

inline bool GetIsDeviceIndependent() const

Returns true if this exception is device independent.

For exceptions that are not device independent, viskores::TryExecute, for example, may try executing the code on other available devices.

There are several subclasses to viskores::cont::Error. The specific subclass gives an indication of the type of error that occurred when the exception was thrown. Catching one of these subclasses may help a program better recover from errors.

class ErrorBadAllocation : public viskores::cont::Error

This class is thrown when Viskores attempts to manipulate memory that it should not.

class ErrorBadDevice : public viskores::cont::Error

This class is thrown when Viskores performs an operation that is not supported on the current device.

class ErrorBadType : public viskores::cont::Error

This class is thrown when Viskores encounters data of a type that is incompatible with the current operation.

class ErrorBadValue : public viskores::cont::Error

This class is thrown when a Viskores function or method encounters an invalid value that inhibits progress.

class ErrorExecution : public viskores::cont::Error

This class is thrown in the control environment whenever an error occurs in the execution environment.

class ErrorFilterExecution : public viskores::cont::Error

This class is primarily intended to filters to throw in the control environment to indicate an execution failure due to misconfiguration e.g.

incorrect parameters, etc. This is a device independent exception i.e. when thrown, unlike most other exceptions, Viskores will not try to re-execute the filter on another available device.

class ErrorInternal : public viskores::cont::Error

This class is thrown when Viskores detects an internal state that should never be reached.

This error usually indicates a bug in viskores or, at best, Viskores failed to detect an invalid input it should have.

class ErrorUserAbort : public viskores::cont::Error

This class is thrown when viskores detects a request for aborting execution in the current thread.

class ErrorIO : public viskores::cont::Error

This class is thrown when Viskores encounters an error with the file system.

This can happen if there is a problem with reading or writing a file such as a bad filename.

2.9.2. Asserting Conditions

In addition to the aforementioned error signaling, the viskores/Assert.h header file defines a macro named VISKORES_ASSERT. This macro behaves the same as the POSIX assert macro. It takes a single argument that is a condition that is expected to be true. If it is not true, the program is halted and a message is printed. Asserts are useful debugging tools to ensure that software is behaving and being used as expected.

VISKORES_ASSERT(condition)

Asserts that condition resolves to true.

If condition is false, then a diagnostic message is outputted and execution is terminated. The behavior is essentially the same as the POSIX assert macro, but is wrapped for added portability.

Like the POSIX assert macro, the check will be removed when compiling in non-debug mode (specifically when NDEBUG is defined), so be prepared for the possibility that the condition is never evaluated.

The VISKORES_NO_ASSERT cmake and preprocessor option allows debugging builds to remove assertions for performance reasons.

Example 2.63 Using VISKORES_ASSERT.
1template<typename T>
2VISKORES_CONT T GetArrayValue(viskores::cont::ArrayHandle<T> arrayHandle,
3                              viskores::Id index)
4{
5  VISKORES_ASSERT(index >= 0);
6  VISKORES_ASSERT(index < arrayHandle.GetNumberOfValues());

Did You Know?

Like the POSIX assert, if the NDEBUG macro is defined, then VISKORES_ASSERT will become an empty expression. Typically NDEBUG is defined with a compiler flag (like -DNDEBUG) for release builds to better optimize the code. CMake will automatically add this flag for release builds.

Common Errors

A helpful warning provided by many compilers alerts you of unused variables. (This warning is commonly enabled on Viskores regression test nightly builds.) If a function argument is used only in a VISKORES_ASSERT, then it will be required for debug builds and be unused in release builds. To get around this problem, add a statement to the function of the form (void)variableName;. This statement will have no effect on the code generated but will suppress the warning for release builds.

2.9.3. Compile Time Checks

Because Viskores makes heavy use of C++ templates, it is possible that these templates could be used with inappropriate types in the arguments. Using an unexpected type in a template can lead to very confusing errors, so it is better to catch such problems as early as possible. The VISKORES_STATIC_ASSERT macro, defined in viskores/StaticAssert.h makes this possible. This macro takes a constant expression that can be evaluated at compile time and verifies that the result is true.

In the following example, VISKORES_STATIC_ASSERT and its sister macro VISKORES_STATIC_ASSERT_MSG, which allows you to give a descriptive message for the failure, are used to implement checks on a templated function that is designed to work on any scalar type that is represented by 32 or more bits.

Example 2.64 Using VISKORES_STATIC_ASSERT.
1template<typename T>
2VISKORES_EXEC_CONT void MyMathFunction(T& value)
3{
4  VISKORES_STATIC_ASSERT(
5    (std::is_same<typename viskores::TypeTraits<T>::DimensionalityTag,
6                  viskores::TypeTraitsScalarTag>::value));
7
8  VISKORES_STATIC_ASSERT_MSG(sizeof(T) >= 4,
9                             "MyMathFunction needs types with at least 32 bits.");

Did You Know?

In addition to the several trait template classes provided by Viskores to introspect C++ types, the C++ standard type_traits header file contains several helpful templates for general queries on types. Example 2.64 demonstrates the use of one such template: std::is_same.

Common Errors

Many templates used to introspect types resolve to the tags std::true_type and std::false_type rather than the constant values true and false that VISKORES_STATIC_ASSERT expects. The std::true_type and std::false_type tags can be converted to the Boolean literal by adding ::value to the end of them. Failing to do so will cause VISKORES_STATIC_ASSERT to behave incorrectly. Example 2.64 demonstrates getting the Boolean literal from the result of std::is_same.