frans
frans

Reputation: 9788

Recommended way to initialize a const variable with an expression which might throw

You probably know situations like this where you just want to assign to a (const) variable with an expression which might fail (throw) (e.g.container.at()) which forces you to write boiler plate code:

void foo(const string &key) {
    auto it = data_store.find(key);
    if (it == data_store.end()) {
        return;
    }
    const auto & element = it->second;
    ...
    go on with `element`...
    ...
}

In Python you could write code like this:

def foo(name):
    try:
        element = data_store[key]
    except KeyError:
        return
    ..
    go on with `element`
    ..

.. with is less noisy because you don't introduce that useless extra it just for checking existence.

If C++'s try would not introduce a variable scope you could just use at():

void foo(const string &key) {
    try {
        const auto & element = data_store.at(key);
    } catch (const out_of_range &) {
        return;
    }
    ...
    go on with `element`...
    ...
}

What's the way to go here if you don't want to abandon constness and keep your code clean?

If lambdas only could have a try/catch body you could write

void foo(const string &key) {
    const auto & element = [&] () -> T try {
        return data_store.at(key);
    } catch () {
        return;
    } ();
    ...
    go on with `element`...
    ...
}

Some answers to similar questions suggest try/catch blocks around all the code:

void foo(const string &key) {
    try {
        const auto & element = data_store.at(key);
        ...
        go on with `element`...
        ...
    } catch (const out_of_range &) {
        return;
    } catch (some other exception) {
        ...
    } catch (some other exception) {
        ...
    }
}

But I don't like this because of three reasons:

Which (nice, short and clean) alternatives do you know?

Upvotes: 5

Views: 387

Answers (1)

M.M
M.M

Reputation: 141628

There are three good options on this thread, there's not really any other option.

Those cases assume we are initializing an object; for initializing a reference as you are, apply the techniques to std::reference_wrapper, or a pointer.


BTW I would not discount your first code sample so quickly. It's simpler than all the other options, and it is a common recommendation in C++ to only use exceptions for exceptional conditions -- things you do not expect to be a normal part of the function's contract. It's not idiomatic to use them as a shortcut.

In other words, if the function design is to do nothing if the lookup fails, then throw-catching is an unnecessary complication to the function. You've just written an even uglier version of C-style error handling.

The whole point of the at() accessor is that your function can be kept simple by not catching -- the exception can be left to propagate up to a more general error handler.

Upvotes: 2

Related Questions