I'm dealing with an application, that needs to read in 10+ CSV-files (of different kind) as input. The data is read into a container -- std::map
or vector
.
Previously each kind had its own parsing function, and I'm working on unifying that into a single templatized function: to have save on future code-maintenance and provide uniform error-reporting for broken files.
This function reads each line, discerns, whether the container type has the concept of a key
(like map
) and uses emplace
for those, and emplace_back
for the others (like vector
).
The only expectation from the value class of the container is that its constructor can instantiate from a CSV-line. Any exception by the constructor is a fatal error -- the input filename and line-number are reported, and the program exits:
try {
if constexpr (is_associative_container<Container>(NULL)) {
result->emplace(typename Container::key_type(
key, keylen), value);
} else {
result->emplace_back(value);
}
} catch (const std::exception &e) {
fprintf(stderr, "%s:%zd: %s\n", path, line, e.what());
goto failure;
}
This all works and I'm happy -- about 75% of the CSV-parsing is now done by this function.
I'm now facing the remaining quarter of the CSV-files, which are less straightforward: because certain rows in them require special treatment and their content isn't supposed to be stored in the container.
How can the value_type
's constructor signal to the function, that the exception it is throwing should not considered fatal? One way is to pick one of the standard exceptions (std::bad_function_call
?) as a signal, but that'd mean, the picked exception mustn't occur unexpectedly -- which is unreliable...
Anything else?
A question edit overlapped with writing this answer. The original answer is below.
How can the value_type's constructor signal to the function, that the exception it is throwing should not considered fatal?
Note that in your current code you already do make a distinction between exceptions inherited from std::exception
and those that do not inherit from std::exception
. Good style is to inherit all exceptions from std::exception
but reality is usually different.
You could introduce some special type of exception to be thrown:
try {
//...
} catch (const non_fatal_exception &e) {
// do something
} catch (...) { // all other exceptions are "fatal"
fprintf(stderr, "%s:%zd: %s\n", path, line, e.what());
goto failure;
}
Old answer about dynamic exception specifications...
As mentiond in comments, dynamic exception specifications are removed from C++17.
Before C++17 a function that did throw an exception not listed in the exception specification did the following (from cppreference):
If the function throws an exception of the type not listed in its exception specification, the function
std::unexpected
is called. The default function callsstd::terminate
, but it may be replaced by a user-provided function (viastd::set_unexpected
) which may callstd::terminate
or throw an exception. If the exception thrown fromstd::unexpected
is accepted by the exception specification, stack unwinding continues as usual. If it isn't, butstd::bad_exception
is allowed by the exception specification,std::bad_exception
is thrown. Otherwise,std::terminate
is called.
There is no way out unless you know some exception that would be accepted from the exception specification, but in general you do not know that. I am not aware of deducing the "allowed" exceptions in generic code. Anyhow, the feature is removed. And in some sense it was already "fatal" to throw an exception not listed in the exception specification, its not something you have to do extra.