January 13, 2019

Code verbosity is one of those topics that everyone has an opinion on – and mine will probably differ from yours. One of my particular quirks is that I like my code to fit in 80 columns wherever possible (or 132 on some codebases). It’s just one of those things.

The C++11 enum class concept, as nice as it is, can increase the verbosity of your code. It’s a fine balance to get the right level, and in large switch statements for example, enum class values can get very annoying.

Enter the C++11 version of the using statement, and you can clean this up fairly easily…

What?

I’m in the process of putting together the infrastructure for a client library for one of my projects (my next home automation system, if you must know; it’s early days yet, so not much to show there). One of the things I need is to manage error messages.

In other words, I had to write the client’s equivalent of strerror()

It’s just a big switch statement that returns strings really; I’m not going for anything fancy here. But all the typing involved in enum classes gets fairly annoying, is fairly useless, and can make for some very long lines.

So how do you clean it up?

Before

This is roughly what the code looked like in my fledgling client before I “cleaned it up” (YMMV on that):

std::string to_string(client::client_error err) {
  switch(err) {
  case client::client_error::NONE: return "no error";
  case client::client_error::NO_BROKER: return "no broker available for messaging";
  case client::client_error::BROKER_CONNECT_FAILED: return "failed to connect to broker";
  case client::client_error::NEGOTIATE_NAME_FAILED: return "failed to negotiate interface name";
  case client::client_error::CONNECT_FAILED: return "failed to connect";
  }
}

Pretty simple, but as the list of possible errors grows it’s going to get very long, and all that client::client_error stuff is going to take up a lot of useless extra space. So instead I decided to try something a little “clever” (though real C++ programmers probably already know this stuff – I’m more a “C with classes” guy).

After

In the olden days, I might have decided to use something like #define ERR(x,y) case client::client_error::x: return y to make this more readable. I’m really not a fan of macros either, though; if you forget to undefine them, they can cause all sorts of random issues down the line. Not to mention they can be pretty ugly.

In the modern day, I can get at least partway there with the using statement, however:

std::string to_string(client::client_error err) {
  using E = client::client_error;

  switch(err) {
  case E::NONE: return "no error";
  case E::NO_BROKER: return "no broker available for messaging";
  case E::BROKER_CONNECT_FAILED: return "failed to connect to broker";
  case E::NEGOTIATE_NAME_FAILED: return "failed to negotiate interface name";
  case E::CONNECT_FAILED: return "failed to connect";
  }
}

using gives you a nice way to alias a type so you don’t have to go through all that typing. It’s still fairly verbose – the #define method would make things a bit shorter – but this way I can tell exactly what it’s doing, while still looking halfway decent and not having 300-character lines.

It’s a useful technique to know about, with implications far beyond my silly little error message dictionary. And when you’re referencing a type that’s four namespaces deep inside a class that’s inside another class…

It can be pretty damn useful.