Document number: N3757
Programming Language C++, Library Subgroup
 
Emil Dotchevski, emil@revergestudios.com
 
2013-08-30

Support for user-defined exception information and diagnostic information in std::exception

I. Overview

C++ supports throwing objects of user-defined types, which allows arbitrary data to be stored in exception objects at the point of the throw. Unfortunately at that time we may not have available all of the data that is needed at the catch site to properly deal with the error. However, that data is usually available in contexts higher in the call stack, between the throw and the catch that handles the exception.

This document proposes extending std::exception to allow exception-neutral contexts to intercept any std::exception object and store into it arbitrary additional data needed to handle the exception (the propagation of the original exception object can then be resumed by a throw without argument.)

II. Motivation

To illustrate the problem, consider the following example of a catch statement that handles an exception of a user-defined type file_read_error:

catch( file_read_error & e )
{
  std::cerr << e.file_name();
}

And the matching throw:

void read_file( FILE * f )
{
  ....
  size_t nr=fread(buf,1,count,f);
  if( ferror(f) )
    throw file_read_error(???);
  ....
}

The issue is that the catch site needs a file name, but at the point of the throw a file name is not available (only a FILE pointer is.)

Consider that the error might be detected in a library which can not assume that a meaningful name is available for any FILE it reads, even if a program that uses the library could reasonably make the same assumption.

The proposed extension to std::exception allows a program to augment library exceptions with program-specific information required to properly handle them:

struct errinfo_file_name { typedef std::string type; };

void process_file( char const * name )
{
  try
  {
    if( FILE * fp=fopen(name,"rt") )
    {
      std::shared_ptr<FILE> f(fp,fclose);
      ....
      read_file(fp); //throws types deriving from std::exception
      ....
    }
    else
      throw file_open_error();
  }
  catch( std::exception & e )
  {
    e.set<errinfo_file_name>(name);
    throw;
  }
}

The handler can retrieve the file name using the following syntax:

catch( file_io_error & e ) //derives (possibly indirectly) from std::exception
{
  std::cerr << "I/O error!\n";
  std::string const * fn=e.get<errinfo_file_name>(e);
  assert(fn!=0); //In this program all files have names.
  std::cerr << "File name: " << *fn << "\n";
}

III. Impact

Extending std::exception as proposed is compatible with existing code and does not affect any other parts of the standard library, except possibly make_exception_ptr and current_exception.

IV. Proposed Text

  1. Add to std::exception the following member functions:

    template <class Tag>
    void set( typename Tag::type const & x );

    Requirements: x shall be copyable.

    Effects: A copy of x is stored in this. If this already has a value stored under the specified Tag, the original value is overwritten.

    Postconditions: get<Tag>() returns a pointer to the stored copy of x.

    Throws: std::bad_alloc or any exception thrown by T's copy or move constructor.

    Note: It is permitted for copies of a std::exception object to share storage for the objects stored by set<>, except for copies created by std::make_exception_ptr or std::current_exception. Therefore calling set<> on copies of a std::exception is not thread-safe unless the copies were created by std::make_exception_ptr or std::current_exception.

    template <class Tag>
    typename Tag::type const * get() const;

    Returns: If set<Tag> was called to store a copy of an object of type typename Tag::type in this, get<Tag> returns a pointer to that copy; otherwise it returns 0.

    Throws: does not throw.

    Note: Destroying the exception object or calling set<> invalidates the returned pointer.

    std::string diagnostic_information() const;

    Returns: A string of unspecified format that contains human-readable diagnostic information about this.

    Throws: May throw std::bad_alloc or any exception thrown in the attempt to convert to string any of the objects stored in this by set<>.

    Notes:

    Example:

    example_io.cpp(70): Throw in function class std::shared_ptr<FILE> open_file(const char *,const char *)
    Dynamic exception type: struct file_open_error
    what: example_io error
    errinfo_api_function = fopen
    errinfo_errno = 2, "No such file or directory"
    errinfo_file_name = tmp1.txt
  2. Add the following note to the specification of current_exception (18.8.5 [propagation]):

  3. Add the following note to the specification of make_exception_ptr (18.8.5 [propagation]):

V. Implementability

A proof of concept implementation is available in Boost. See boost/exception/N3757.hpp and libs/exception/test/N3757_test.cpp in http://svn.boost.org/svn/boost/trunk.

VI. Acknowledgements

This proposal is based on the Boost Exception library and incorporates valuable feedback from the Boost community. Special thanks to Peter Dimov and Dave Abrahams.