8

Using C++ I'd like to do something along the lines of:

  1. Try to get a reference to something in, say, a map
  2. If it throws, then return straight away
  3. Otherwise, go and use the reference

However because we can't declare a reference object without initialising in C++, then we can't do this directly.

For example if I was happy to pass by value, I could do:

std::map<char, int> mymap {...};
int x;
try {
    x = m.at('a');
} catch (out_of_range& e) {
    return;
}
// do something with x

However because I need a reference, I would have to do the following which won't compile.

std::map<char, int> mymap {...};
int& x;
try {
    x = m.at('a');
} catch (out_of_range& e) {
    return;
}
// do something with x

So the options I can see are (1), use a pointer, which I'd like to avoid, or (2), use a different map method which results in more verbose code, such as:

std::map<char, int> mymap {...};
auto x_it = m.find('a');
if (x_it == m.end()) {
    return;
}
int& x = x_it->second;
// do something with x

Are those the only options or, is there another way of doing this neatly?

Alex
  • 183
  • 1
  • 4
  • [What is a smart pointer and when should I use one?](http://stackoverflow.com/q/106508/102937) – Robert Harvey Aug 11 '16 at 01:34
  • 1
    It would be nice to avoid having to make values of the map smart pointers just for this use case. – Alex Aug 11 '16 at 02:18
  • 2
    "*which results in more verbose code, such as*" It's the same number of lines of code, either way. So why do you say that it's more verbose? Indeed, I'd go so far as to say that `catch(~something~) return;` is an anti-pattern. If you're catching an exception, you had better be doing something more than just returning. – Nicol Bolas Aug 11 '16 at 03:33
  • It is the same number of lines, but it has more logic, i.e., "is it ok? then do it, otherwise handle problem" vs "do it, but if not OK handle problem." I feel the [forgiveness rather than permission](https://docs.python.org/2/glossary.html#term-eafp) style is cleaner. Plus it avoids the risk of the state changing between the check that it's ok to proceed and actually accessing the element. – Alex Aug 11 '16 at 04:55
  • 4
    The way your code looks, the exception being thrown is a normal use-case. As such, I would strongly advise against using the throwing member of `std::map<>`: The performance hit of a thrown exception is quite large (on the order of a micro second, if I remember correctly). You can do quite a lot of useful things in that time. – cmaster - reinstate monica Aug 11 '16 at 09:48

2 Answers2

11

The normal way is to move the "so something with x" line(s) also within the try block:

std::map<char, int> mymap {...};
try {
    int& x = mymap.at('a');
    // do something with x
} catch (out_of_range& e) {
    return;
}

The nice thing about exceptions is that when an exception is thrown, all code between the throw and catch locations is completely skipped over.
This means that you can write the code within the try { ... } catch block with the knowledge that everything has worked fine if you reach that line.

Bart van Ingen Schenau
  • 71,712
  • 20
  • 110
  • 179
  • Thanks. This solves the problem, with the only downside being if `// do something with x` is long, then you end up with a large block of code inside the try block. I guess this can be resolved with some refactoring. – Alex Aug 11 '16 at 23:23
  • 2
    @Alex: More relevantly, you'll swallow any `out_of_range` exceptions that `// do something with x` throws, which probably isn't what you want. – Eric Mar 29 '18 at 19:31
1

If you had wanted to return a reference from both the try and the catch branch, it would be easy:

std::map<char, int> mymap {...};
int &x = [&]() -> int & { try {
    return mymap.at('a');
} catch (std::out_of_range& e) {
    return some_default_value;
} } ();
// do something with x

But in your case, only the try branch returns a reference, so the return type of the entire try block is not a int & but more like optional<int &>. The problem is that C++17 is not out yet.

Nonetheless, optional<int &> is isomorphic to int *, so ultimately using a pointer here seems like the right thing to do. If you don't like the -> syntax, you can just reassign int &y = *x;.

Rufflewind
  • 2,217
  • 1
  • 15
  • 19