How can a constructor signal to caller, whether an exception it is throwing is fatal?

106 Views Asked by At

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?

3

There are 3 best solutions below

1
463035818_is_not_an_ai On BEST ANSWER

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 calls std::terminate, but it may be replaced by a user-provided function (via std::set_unexpected) which may call std::terminate or throw an exception. If the exception thrown from std::unexpected is accepted by the exception specification, stack unwinding continues as usual. If it isn't, but std::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.

14
Mooing Duck On

If it's normal that the row shouldn't go into the container, then that's not an exceptional flow, and you shouldn't be using exceptions. The function should take another parameter: Function<bool(RowData)> shouldInsertIntoContainer

4
Mikhail T. On

Ok, based on my own inclination -- and the suggestions by @Eljay (comment) and @463035818_is_not_an_ai (accepted answer), I modified the code thus:

        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::string &message) {
            /*
             * Allow constructors to fail without aborting the
             * parsing of the file by throwing an std::string.
             * If the string is not empty, output it to stderr.
             */
            if (!message.empty())
                fprintf(stderr, "%s:%zd: %s\n", path, line,
                    message.c_str());
            continue;
        } catch (const std::exception &e) {
            fprintf(stderr, "%s:%zd: %s\n", path, line, e.what());
            goto failure;
        } catch (...) {
            fprintf(stderr, "%s:%zd: internal error while "
                "emplacing into %s\n",
                path, line, typeid(Container).name());
            goto failure;
        }