Search code examples
c++exceptionboost

Adding error_info to std::exception


I'm integrating boost::exception into existing code. Some of the code now uses BOOST_THROW_EXCEPTION, but some might still throw standard std::exception.

I want to add error_info at an intermediary catch site. According to the docs, if the exception is a boost::exception, I can just do this:

try {
    do_something()
}
catch( boost::exception & e ) {
    e << boost::errinfo_file_name(file_name);
    throw;
}

But that will only add the info to the boost exceptions. I want to add it to the std::exception as well. What's the cleanest way to do that? Here's one way, but it results in some code duplication:

try {
    do_something()
}
catch( boost::exception & e ) {
    e << boost::errinfo_file_name(file_name);
    throw;
}
catch( std::exception & e ) {
    throw enable_error_info(e) << boost::errinfo_file_name(file_name);
}

Is there a method that's equivalent to "give me the current exception as a boost exception, or create a boost exception out of it if it isn't one"?

EDIT: boost::enable_error_info() kinda does that but returns a copy of the original exception, which slices off the boost::exception part of the exception I'm catching. Case in point:

int main()
{
    try {
        try {
            BOOST_THROW_EXCEPTION( std::runtime_error( "foo" ) );
        }
        catch( std::exception & e ) {
            std::cerr << e.what() << std::endl; // "foo" 
            if( const char* const* function = boost::get_error_info<boost::throw_function>(e) ) std::cerr << *function << std::endl; // "int main()"
            throw boost::enable_error_info(e) << boost::errinfo_file_name("bar");
        }
    }
    catch( std::exception & e ) {
        std::cerr << e.what() << std::endl; // "std::exception" 
        if( const char* const* function = boost::get_error_info<boost::throw_function>(e) ) std::cerr << *function << std::endl; // NOTHING
    }
    
    return 0;
}

EDIT: I tried using boost::current_exception(), and it slices things off too. Basically, any attempt to copy an exception will lose some of the data because of the slicing caused by multiple inheritance. Same reason as why the docs say you should always rethrow using throw instead of throw e. So I really don't want to incur any copying unless it's necessary.

Ideally, I'd like to write the following, where current_exception_as_boost_exception() returns a reference to the current exception if it is already a boost::exception, otherwise returns the result of calling boost::enable_error_info on it.

try {
    do_something()
}
catch( std::exception & e ) {
    throw current_exception_as_boost_exception() << boost::errinfo_file_name(file_name);
}

Is that what boost::enable_current_exception is for? It's really unclear what its purpose is, and it's not used in any of the tutorials.


Solution

  • Here is a solution that does what I want. But if feels like I'm reinventing something here. Isn't there a built-in way to achieve the same thing?

    struct rethrow
    {
        rethrow()
        {
            try{ throw; }
            // Already a boost::exception
            catch( boost::exception& ) {} 
            // Something else. Make it a boost::exception
            catch( ... ) { ptr = boost::current_exception(); } 
        }
    
        template<class T> 
        rethrow const& operator<<( const T& t ) const
        {
            try
            {
                re();
            }
            catch( boost::exception& e )
            {
                e << t;
            }
            return *this;
        }
    
        ~rethrow()
        {
            re();
        }
    
    private:
        void re() const
        {
            if( !ptr ) throw;
            else boost::rethrow_exception( ptr );
        }
    
        boost::exception_ptr ptr; 
    };
    
    
    int main()
    {
        try {
            try {
                throw std::runtime_error( "foo" ); // or BOOST_THROW_EXCEPTION( std::runtime_error( "foo" ) );
            }
            catch( std::exception & e ) {
                rethrow() << boost::errinfo_file_name("bar");
            }
        }
        catch( std::exception & e ) {
            std::cerr << __LINE__ << ": caught " << e.what() << std::endl; // "caught foo"
            if( const char* const* function = boost::get_error_info<boost::throw_function>(e) ) std::cerr << __LINE__ << ": throw from " << *function << std::endl; // "throw from int main()" (when using BOOST_THROW_EXCEPTION)
            if( std::string const* fileName = boost::get_error_info<boost::errinfo_file_name>(e) ) std::cerr << __LINE__ << ": trying to open " << *fileName << std::endl; // "trying to open bar"
        }
    
        return 0;
    }