Ch19.4: <stdexcept>
1. The Header Everyone Includes
<stdexcept> is the class library that came with legacy exceptions, and it
is the one header that every C++ codebase includes without thinking. Its entire content is
a two-branch tree below ::std::exception:
std::exception what() -> char const *
├── std::logic_error "violated a precondition the caller could have checked"
│ ├── std::invalid_argument
│ ├── std::domain_error
│ ├── std::length_error
│ └── std::out_of_range
└── std::runtime_error "only detectable while running"
├── std::range_error
├── std::overflow_error
├── std::underflow_error
└── std::system_error (<system_error>, Ch19.5)
Four more classes live in other headers and are thrown by the runtime itself:
::std::bad_alloc and ::std::bad_array_new_length in
<new>, ::std::bad_cast and
::std::bad_typeid in <typeinfo>. Every one of them stores a
message string and returns it from a virtual what().
2. What the Split Means, and Why It Carries Nothing
The standard's intent is: logic_error is for a bug in the caller, a
precondition that could have been checked before the call; runtime_error is for
something only the running program can discover. That sounds like information, and it is
the reason tutorials spend a page on it.
It carries nothing. No compiler checks it, no catch site can act on it, and the
distinction is invisible at the call site because the function's signature does not
mention the type. A caller in a catch (::std::exception const &) has no
way to tell whether it violated a precondition or hit a full disk, and after the fact it
does not matter: the recovery is the same "give up on this operation" path. What actually
carries information is the code, which is what a herbception puts in its two words
(Ch3.16).
| Class | The standard's intent | What a program can do with it |
|---|---|---|
invalid_argument |
A parameter was wrong | Nothing; it arrives after the call |
out_of_range |
An index or key was outside its domain | Nothing; the check it describes is one you should have made yourself |
length_error |
A container cannot hold that many elements | Print a message nobody reads |
overflow_error / underflow_error |
Arithmetic went out of range | Nothing |
bad_alloc |
Memory ran out | Almost never anything correct — Ch19.2 argues it should terminate |
3. Constructing One, and What It Stores
The constructors are (char const *) and (::std::string const &).
The message is stored inside the object, which means the constructor of a
runtime_error with a long message allocates — on the
failure path, before the exception has even been thrown.
what() returns char const *, and in this book that is not
printable as text (Ch5.8): you must
say what the pointer means, so every example here writes
::fast_io::mnp::os_c_str(e.what()). A C string is a convention, not a type.
To add anything of your own to a legacy exception you must derive from one of these classes
and pass a message up to it. There is no other way: the base class owns the only channel
out. Here is a parser error with a line and a column, caught as its base, next to the
standard library's own length_error from a container:
#include <fast_io.h>
#include <stdexcept>
#include <string>
#include <vector>
/* the only way to add data to a legacy exception: derive and pass a message up */
struct parse_error : ::std::runtime_error
{
::std::size_t line;
::std::size_t column;
parse_error(::std::size_t l, ::std::size_t c)
: ::std::runtime_error("invalid token"), line(l), column(c)
{}
};
int main()
{
try
{
throw parse_error(12, 34);
}
catch (::std::exception const &e)
{
::fast_io::io::print("as exception ", ::fast_io::mnp::os_c_str(e.what()), "\n");
}
try
{
::std::vector<int> v;
v.resize(static_cast<::std::size_t>(1) << 63);
}
catch (::std::exception const &e)
{
::fast_io::io::print("as length ", ::fast_io::mnp::os_c_str(e.what()), "\n");
}
}
as exception invalid token
as length vector::_M_default_append
Two things about that transcript. First, the parse_error's own data
— line and column — is gone at the
catch site: the handler was written for ::std::exception, so the only thing it
can reach is what(). The extra data exists, was paid for, and cannot be
recovered without RTTI and a dynamic_cast
(Ch18.5).
Second, the standard library's message is vector::_M_default_append —
the name of a private member function, not a sentence about the user's problem. That is what
what() is in practice: an implementation detail string, in English, that no
program can parse and no user can act on.
4. The Bug the Header Invites: Catching by Value
catch (T e) is legal for every T, and for a polymorphic
T it copies only the T part of the exception object. The rest of
the object is sliced off, including the vtable, so the what() you call through
the base is the base's:
#include <fast_io.h>
#include <stdexcept>
int main()
{
try
{
throw ::std::out_of_range("index 7 is past the end");
}
catch (::std::exception e)
{
::fast_io::io::print("what ", ::fast_io::mnp::os_c_str(e.what()), "\n");
}
}
slice.cc: In function 'int main()':
slice.cc:10:33: warning: catching polymorphic type 'class std::exception' by value [-Wcatch-value=]
10 | catch (::std::exception e)
| ^
what std::exception
The program threw out_of_range("index 7 is past the end") and printed
std::exception. The message is not lost, it is sliced away: the copy
the handler owns is a ::std::exception subobject, and its what()
says so. The compiler will tell you about it if you ask for warnings
(-Wcatch-value), and every legacy style guide has a line about catching by
const& because of this.
5. What Replaces It
Nothing in the herbceptions world corresponds to <stdexcept>, and nothing
needs to. An error is a code in a domain
(Ch19.1): the code says
what happened, the domain says what the code means, and to_errc() gives every
caller a common tongue. There is no message string to allocate, no base class to derive
from, no virtual what() to slice, and no handler ordering to get wrong.
When a legacy library throws one of these at you anyway, it arrives as a
::std::error with a domain and a message
(Ch19.6), which is the whole
reason the interop layer exists.