1

In go a common way to do error handling and still return a value is to use tuples.

I was wondering if doing the same in C++ using std::tie would be a good idea when exceptions are not applicable.

like

std::tie(errorcode, data) = loadData();
if(errorcode)
  ...//error handling

Are there any downsides to doing so (performance or otherwise)? I suppose with return value optimization it doesn't really make a difference but maybe I'm wrong.

One potential problematic case that I could see is the use in a cross-compiler API but that's not specific to this use.

The current way I do this is

errorcode = loadData(&data);
if(errorcode)
  ...//error handling

but that allows to pass in a value for data.

The errorcode itself is something that is already defined and that I can't change.

Edit: I'm using/have to use C++11

Jimmy R.T.
  • 1,314
  • 1
  • 10
  • 13
  • 6
    Better is C++17 and structured bindings, thus not needing to declare variables to `tie` the results into: `auto const [errorcode, data] = loadData();`. And when you don't need an error code, just the presence or absence of a result, use `std::optional`. – underscore_d Nov 19 '19 at 13:02
  • 1
    Even better would be using `variant` to be either an error or a result value. But using `variant` is painful, at least in C++11 (where you even have to backport it). – ziggystar Nov 19 '19 at 13:04
  • What the point, enum is usualy used. As well as it's better to use reference on `std::error_code&` as the first paramter, and use return for anything else. You can check custom error [code/condition](https://github.com/incoder1/IO/blob/master/src/charsetcvt.cpp) I using for iconv character set conversion – Victor Gubin Nov 19 '19 at 13:04
  • I should have clarified that I'm asking specifically for C++11 – Jimmy R.T. Nov 19 '19 at 13:06
  • 1
    @user13676 You can backport `optional` and `variant` easily using header only libraries. – ziggystar Nov 19 '19 at 13:07
  • @ziggystar e.g. they can use Boost, from which these classes were ported to `std`. – underscore_d Nov 19 '19 at 13:08
  • https://github.com/TartanLlama/expected – Evg Nov 19 '19 at 13:18

3 Answers3

2

Sometimes output parameters are very handy. Suppose that loadData returns std::vector<T> and is called in a loop:

std::pair<ErrorCode, std::vector<T>> loadData();

for (...) {
    ErrorCode errorcode;    
    std::vector<T> data;
    std::tie(errorcode, data) = loadData();
}

In this case loadData will have to allocate memory on each iteration. However, if you pass data as the output parameter, previously allocated space can be reused:

ErrorCode loadData(std::vector<T>&);

std::vector<T> data;
for (...) {
    ErrorCode errorcode = loadData(data);
}

If the above is of no concern, then you might want to take a look at expected<T, E>. It represents either

  • a value of type T, the expected value type; or
  • a value of type E, an error type used when an unexpected outcome occurred.

With expected, loadData() signature might look like:

expected<Data, ErrorCode> loadData();

C++11 implementation is available: https://github.com/TartanLlama/expected

Evg
  • 25,259
  • 5
  • 41
  • 83
1

There are multiple competing strategies for error handling. I will not go into it, as it is beyond the scope of the question, but error handling by return error codes is only one option. Consider alternatives like std::optional or exceptions, which are both common in C++, but not in Go.

If you have a function that is intended to return a Go-style error code plus value, then your std::tie solution is perfectly fine in C++11 or C+14, although in C++17, you would prefer structured bindings instead.

Philipp Claßen
  • 41,306
  • 31
  • 146
  • 239
  • What if the error is a common case? Aren't exception a bad choice then? – Jimmy R.T. Nov 19 '19 at 13:15
  • 2
    @user13676 I'm not saying you have to use exceptions. There are certainly situations where error codes, or returning std::optional, are better solutions. Herb Sutter is working on true zero costs exceptions, which is interesting, but not available yet: www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0709r0.pdf – Philipp Claßen Nov 19 '19 at 13:24
0

Are there any downsides to doing so (performance or otherwise)?

Yes. With tie, a copy or move of the returned values is required that would not be required if you avoid tie:

auto result = loadData();
if (std::get<0>(result))
    ...//error handling

Of course, if you would later copy or move the data somewhere else anyway, like in

data = std::move(std::get<1>(result));

then use tie because it is shorter.

j6t
  • 9,150
  • 1
  • 15
  • 35
  • Return value optimization doesn't apply in this case? – Jimmy R.T. Nov 19 '19 at 14:58
  • @user13676 Return value optimization is independent from the use case that is discussed here. In all cases, the caller has to provide some space where the result of the function call is written to (with or without RVO). With `auto` (without `tie`), that's all that is needed. But with `tie`, the parts are moved (or copied) into the destination; this is additional work. – j6t Nov 19 '19 at 20:12