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.