0

I am wondering why std::current_exception() works differently in this situation:

std::exception_ptr e, e2;
try {
    std::string("abcd").substr(42);
} catch(std::exception &ex) {
    std::cerr << "(1) Exception: " << ex.what() << std::endl;
    e = std::current_exception();
    e2 = std::make_exception_ptr(ex);
}
handle_exception(e);
handle_exception(e2);

Whereas handle_exception prints the exception:

void handle_exception(std::exception_ptr e)
{
    try {
        if (e)
            std::rethrow_exception(e);
    } catch(const std::exception& e) {
        std::cerr << "Exception: " << e.what() << std::endl;
    }
}

The output is as follows:

(1) Exception: basic_string::substr: __pos (which is 42) > this->size() (which is 4)
(2) Exception: basic_string::substr: __pos (which is 42) > this->size() (which is 4)
(3) Exception: std::exception

However I would expect the following output:

(1) Exception: basic_string::substr: __pos (which is 42) > this->size() (which is 4)
(2) Exception: basic_string::substr: __pos (which is 42) > this->size() (which is 4)
(3) Exception: basic_string::substr: __pos (which is 42) > this->size() (which is 4)

What am I missing here?

2 Answers 2

6

About C++ throw

It's important to note that C++ throw (I will only talk about its usage with operand, like throw someExpression;) is sensitive to type of passed operand. It copies its operand (copy-initializes an auxiliary object from it). And copying is done according to (lexical) type of the operand (not by getting class dynamically).

E.g.:

try {
    std::runtime_error myException("Some test message");
    std::exception &upcastedMyException = myException;
    throw upcastedMyException; //std::exception is thrown
} catch(const std::exception &caught) {
    std::cout << caught.what() << std::endl; //will output "std::exception"
}

It differs from other languages, e.g. Java:

try {
    RuntimeException myException = new RuntimeException();
    Exception upcastedMyException = myException;
    throw upcastedMyException; //RuntimeException is thrown
} catch(Exception caught) {
    System.out.println(caught.getClass()); //will output "java.lang.RuntimeException"
}

Therefore it's very important that expression passed to C++ throw has correct type. In my first example, replacing throw upcastedMyException with (for example) throw static_cast<const std::runtime_error &>(upcastedMyException) would change the output.

About std::make_exception_ptr

std::make_exception_ptr, on its hand, is implemented in quite simple way through throw:

template<class E> std::exception_ptr make_exception_ptr(E e) noexcept {
    try {
        throw e; //note this line
    } catch(...) {
        return std::current_exception();
    }
}

This means that std::make_exception_ptr is sensitive to passed template argument; if template argument isn't passed explicitly, then std::make_exception_ptr is sensitive to (lexical) type of passed function argument (which influences deduction of template argument):

  • If you do std::make_exception_ptr(ex) in your code (where ex is declared as const std::exception &), then std::exception would be thrown within std::make_exception_ptr and std::make_exception_ptr would return std::exception_ptr related to std::exception.
  • If you do std::make_exception_ptr(static_cast<const std::logic_error &>(ex)) in your code, then std::logic_error would be thrown within std::make_exception_ptr and std::make_exception_ptr would return std::exception_ptr related to std::logic_error.
  • If you do std::make_exception_ptr(static_cast<const std::out_of_range &>(ex)) in your code, then std::out_of_range would be thrown within std::make_exception_ptr and std::make_exception_ptr would return std::exception_ptr related to std::out_of_range.
5

std::make_exception_ptr() takes its argument by value. This means either a copy or in-place construction.

You're making a copy of the std::exception (i.e. slicing)

5
  • 2
    I understand it is a copy as described in en.cppreference.com/w/cpp/error/make_exception_ptr. But why would that matter?
    – ypnos
    Commented Aug 10, 2015 at 18:03
  • 1
    @ypnos because std::exception was designed using object oriented inheritance based polymorphism, not type erasure based polymorphism? Commented Aug 10, 2015 at 18:08
  • In the website I linked it says: "This is done as if executing the following code: …" basically doing the same thing as I am doing. And what would be the purpose of std::make_exception_ptr() if it always creates a meaningless copy of the exception?
    – ypnos
    Commented Aug 10, 2015 at 18:09
  • @ypnos std::exception is a base class. You would never write auto x = std::make_exception_ptr(std::exception(...)); Commented Aug 10, 2015 at 18:11
  • 1
    @ypnos you can create an exception pointer from the actual type you want to throw, instead of from the base type? Your code uses std::exception & and std::exception_ptr, neither of which copy or slice. make_exception_ptr lets you create an exception in one context, and throw it in another (say create it in one thread, and throw it in another). It is not intended to capture the current exception, that is what current_exception is for... Commented Aug 10, 2015 at 18:13

Not the answer you're looking for? Browse other questions tagged or ask your own question.