Stingery
Stingery

Reputation: 430

Return exception from function (not throw in function) for message formatting?

I have a function that performs basic string formatting to generate an error description string as input to std::runtime_error:

std::string errormsg(int i)
{
  std::string str;
  // .. do something with str
  return str;
}

... such that:

void some_process(int x)
{
  if (x < 0)
    throw std::runtime_error(errmsg(x));
}

My intuition is that this could be simplified by using a function that directly returns an exception type; using a more complete example:

template<typename ... Ts>
std::runtime_error error(const char * frmt, Ts ... args)
{
    const int n = std::snprintf(nullptr, 0, frmt, args...);
    std::vector<char> buff(n + 1);
    std::snprintf(&buff[0], n + 1, frmt, args...);
    return std::runtime_error(std::string(buff.data()));
}

... which simplifies to throw error("Error code %d", x).

But I found it difficult to find code examples where exception types are returned from a wrapper function. Hence, would the above be considered bad practice, or otherwise unsafe?

Upvotes: 0

Views: 364

Answers (1)

463035818_is_not_an_ai
463035818_is_not_an_ai

Reputation: 122585

There is nothing special about exception types. They are types like others, just that they are meant to be thrown as exception. Consider that you can throw objects of any type not just types inheriting from std::exception.

The reason you did not find examples is perhaps that it is more typical to inherit from std::runtime_error and then just call the constructor when you want to throw it. Something along the line of

struct my_runtime_error : std::runtime_error {
{
    template<typename ... Ts>
    my_runtime_error(const char * frmt, Ts ... args);
};

Or as suggested in a comment, write a function that returns void and instead throws the exception itself:

template<typename ... Ts>
[[ noreturn ]] void throw_error(const char * frmt, Ts ... args)
{
    const int n = std::snprintf(nullptr, 0, frmt, args...);
    std::vector<char> buff(n + 1);
    std::snprintf(&buff[0], n + 1, frmt, args...);
    throw std::runtime_error(std::string(buff.data()));
}

to be used as

void some_process(int x)
{
   if (x < 0)
      throw_error(errmsg(x));
}

However, this has the disadvantage of always throwing from the same function instead of the place where actually the problem occurs and if I had to choose between this and yours I would go for your solution.

PS: note that std::exception and its descendants are meant to be inherited from in contrast to most other types in the standard library. std::exception does have a virtual destructor.

Upvotes: 4

Related Questions