Example: std::range_error Exception

The range_error exception is part of the Standard Library, specifically within the <stdexcept> header. It is a type of exception that is thrown when there is an attempt to use a value that is outside of the valid range for a given operation. For instance, it can be used in cases where a calculation or index goes out of the expected or acceptable range, but the nature of the error is not related to memory (like in the case of out-of-bounds array access).

range_error belongs to a hierarchy of exceptions provided by the Standard Library and is derived from the base class std::runtime_error. The std::runtime_error class is itself derived from std::exception, which is the base class for all standard exceptions in C++.

Hierarchy

Here’s the inheritance hierarchy of range_error:

std::exception
   └── std::runtime_error
         └── std::range_error

Characteristics of std::range_error

  • Header File: <stdexcept>
  • Base Class: std::runtime_error
  • Description: Thrown to indicate that an argument is out of the range of valid values for a particular operation. Unlike std::out_of_range, which is typically associated with container-based out-of-bound access, std::range_error is used in other forms of range violations, typically involving numeric or algorithmic operations.

Syntax

#include <stdexcept>

class range_error : public std::runtime_error {
public:
    explicit range_error(const std::string& what_arg);
    explicit range_error(const char* what_arg);
};
  • Constructor with string message: range_error(const std::string& what_arg) – Takes a string as an argument, which is typically used to describe the cause of the exception.
  • Constructor with C-string message: range_error(const char* what_arg) – Takes a C-style string as a message describing the error.

Both constructors call the base class std::runtime_error to store the error message.

Example of Usage

Here’s an example of how std::range_error might be used in a numeric function:

#include <iostream>
#include <stdexcept>

double divide(double a, double b) {
    if (b == 0) {
        throw std::range_error("Division by zero is not allowed");
    }
    return a / b;
}

int main() {
    try {
        double result = divide(10.0, 0.0);
        std::cout << "Result: " << result << std::endl;
    } catch (const std::range_error& e) {
        std::cerr << "Caught range_error: " << e.what() << std::endl;
    }
    
    return 0;
}

In this example:

  • The divide function checks if the denominator b is zero, and if it is, it throws a std::range_error with an appropriate error message.
  • The main function attempts to perform division, and if an exception is thrown, it catches the range_error and displays the error message using the what() method.

Output:

Caught range_error: Division by zero is not allowed

range_error vs. out_of_range

C++ provides both std::range_error and std::out_of_range exceptions, and while they sound similar, they are used in different contexts:

std::out_of_range

  • Typically used for container-related errors, such as when an index is out of bounds in a std::vector or std::array.Example: Trying to access an element at an index that doesn’t exist in a vector.

std::vector<int> v{1, 2, 3};
v.at(10);  // Throws std::out_of_range exception
std::range_error
  • Usually used for numeric or algorithmic errors where a value falls outside of the expected range.
  • Example: Division by zero or a numeric overflow where the result cannot be represented in the specified range of the type.

Example of std::range_error in Numeric Context

Here’s an example where range_error might be used in a numeric context:

#include <iostream>
#include <stdexcept>
#include <cmath>

double calculate_square_root(double x) {
    if (x < 0) {
        throw std::range_error("Cannot calculate square root of a negative number");
    }
    return std::sqrt(x);
}

int main() {
    try {
        double result = calculate_square_root(-10.0);
        std::cout << "Result: " << result << std::endl;
    } catch (const std::range_error& e) {
        std::cerr << "Caught range_error: " << e.what() << std::endl;
    }
    
    return 0;
}

In this example:

  • The function calculate_square_root checks if the input value x is negative.
  • If x is negative, it throws a std::range_error because square roots of negative numbers are not defined for real numbers.
  • The main function catches the exception and outputs the error message.

Practical Applications of range_error

  • Numeric Range Violations: In numerical computations, range_error can be thrown if the input arguments go beyond what the algorithm or mathematical operation expects (e.g., logarithm of a negative number, division by zero).
  • Algorithmic Constraints: When implementing algorithms that expect parameters or inputs within certain bounds, a range_error can be used to signal when these bounds are violated.
  • Custom Exception Handling: You can define your own operations or classes that throw range_error when inputs or operations produce values outside the defined range.

Handling range_error

Just like any other exception in C++, a range_error exception can be caught using a try-catch block. The what() function can be used to retrieve the error message passed during the throw:

try {
    // Code that may throw a range_error
} catch (const std::range_error& e) {
    std::cerr << "Range error: " << e.what() << std::endl;
}

Conclusion

The std::range_error exception in C++ is used to signal errors related to out-of-range values in numeric or algorithmic contexts. It is part of the std::runtime_error family and can be thrown when calculations exceed the allowable range, such as division by zero or square root of a negative number.