One approach of dealing with the fact that sometimes exceptions are appropriate, sometimes error codes are better is to provide two related functions: one that throws, the other that returns an error code.

Introduction

There are a variety of approaches to providing library APIs that might have errors.

This article provides for reference an example of the approach to provide two related functions: one that throws, the other that return an error code.

Code

error_code.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#pragma once

#include <cstdint>
#include <system_error>

namespace xyz
{
  enum class error_code : int
  {
    some_error = 1,
    another_error = 2
  };

  const std::error_category & error_category();
}

namespace std
{
  template <> struct is_error_code_enum<xyz::error_code> : public std::true_type { };
}

namespace xyz
{
  std::error_code make_error_code(xyz::error_code e);
}

error_code.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include "error_code.h"

#include <sstream>

namespace
{
  class error_category_impl : public std::error_category
  {
    public:
      const char * name() const noexcept override
      {
        return "xyz::error_category";
      }

      std::string message(int value) const override
      {
        switch (static_cast<xyz::error_code>(value))
        {
          case xyz::error_code::some_error:
            return "Some error";
          case xyz::error_code::another_error:
            return "Another error";
          default:
            std::stringstream ss;
            ss << "XYZ error: " << value;
            return ss.str();
        }
      }
  };
}

namespace xyz
{
  const std::error_category & error_category()
  {
    static error_category_impl instance;
    return instance;
  }

  std::error_code make_error_code(xyz::error_code e)
  {
    return std::error_code(static_cast<int>(e), xyz::error_category());
  }
}

error.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#pragma once

#include <system_error>

namespace xyz::error
{
  inline void throw_if_error(std::error_code ec, const char * exception_message)
  {
    if (ec)
    {
      throw std::system_error(ec, exception_message);
    }
  }
}

api.h

1
2
3
4
5
6
7
8
9
#pragma once

#include "error_code.h"

namespace xyz::api
{
  int some_fn(int arg1, int arg2, std::error_code & ec) noexcept;
  int some_fn(int arg1, int arg2);
}

api.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include "api.h"

#include "error.h"

namespace xyz::api
{
  int some_fn(int arg1, int arg2, std::error_code & ec) noexcept
  {
    ec = std::error_code();
    if (arg1 < 0)
    {
      ec = xyz::error_code::some_error;
      return 0;
    }
    if (arg2 < 0)
    {
      ec = xyz::error_code::another_error;
      return 0;
    }
    return (arg1 + arg2);
  }

  int some_fn(int arg1, int arg2)
  {
    std::error_code ec;
    int result = some_fn(arg1, arg2, ec);
    error::throw_if_error(ec, "some_fn");
    return result;
  }
}

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include "api.h"

#include <iostream>

int main()
{
  try
  {
    int result = xyz::api::some_fn(42, 43);
    std::cout << "Result: " << result << '\n';
    result = xyz::api::some_fn(-1, 43);
    std::cout << "Result: " << result << '\n';
  }
  catch (const std::exception & e)
  {
    std::cout << "Exception: " << e.what() << '\n';
  }
}

/* Outputs:
Result: 85
Exception: some_fn: Some error
*/

Issues

One issue with this style of API is that one would expect that the version that has an error code as argument is noexcept. However it’s not so easy in practice as a lot of containers throw std::bad_alloc when running out of memory. So then the options are either:

  • you’re happy to terminate when out of memory
  • you try catch for std::bad_alloc inside the version with error code
  • you use custom containers that provide error codes as arguments instead of throwing (dealing with challenges around copying)

References

Lawrence Crowl: Handling Disappointment in C++
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0157r0.html#both

Chris Kohlhoff: System error support in C++0x - part 1 to 5
http://blog.think-async.com/2010/04/system-error-support-in-c0x-part-1.html