Here is a coding style idea I recently fell in love with. It works in C/C++ thanks to macros.
Take this simple block of code.
For reference, GLContext is an SDL_GLContext which is a void*, so we’re actually dealing with a pointer here.
This above code is fine. It’s relatively short, and cleanly handles typical error cases by returning an error code (zero on success, anything else of failure). What’s interesting is how common this sort of thing is.
Lets change things slightly. Instead, lets say we want to actually return the value of the test. After all, if non-zero is considered an error, then it’s the same thing.
That’s pretty good. The code is shorter, simpler, maybe a little weird thanks to the type conversion (boolean equation to const int), but still okay. I’d like to propose a 3rd version.
All of the above are completely legitimate and perfectly good solutions, but if you’ll hear me out, I want to say the last one is actually the best.
How is it the best? There’s clearly more code and it’s more complicated (a single “=” inside an if block, that’s taboo!). This is how return_if works. The code would then become:
So great Mike, you’ve now created a different and strange dialect of the C and C++ language. Why the hell would you do that? Well, I’d like to present the next code example:
Now things just got complicated. We’re logging different information to standard output depending on the result of the “GLContext == NULL” test. Lets fix this with a new version of return_if.
This is where return_if gets especially interesting. Those several lines of code have been cleanly compressed to 2 lines. The first argument to return_if_printf is the test we were previously doing, and the rest are typical printf arguments.
A barebones implementation of this may look like this.
You’ll note I’m now using Log instead of printf, but as you can see above, Log is printf. Also I use a C++11 auto type, but this could as easily just be an “int” if you wanted to do this in C. The __COUNTER__ stuff is so I generate a unique variable name inside each scope. Technically, due to C/C++ scoping rules I don’t actually need unique variable names, but I do it anyway. To help report better errors, you may want to use a better name than “__Error”… something like “__error_in_return_if__you_probably_meant_to_use_return_if_void”.
Growing the return_if family
Outside the logging, there are a few more variations that would be useful. For one, having a way to to write a return_if inside a void function. In addition, having a way to return a different value upon success. Lets do that right now:
You’ll note I also included if-less versions on the bottom. Just as the names suggest, they work exactly the same but always return. And the ha ha moment, “return” already works with a bracket syntax in C and C++, so we don’t even need a macro for it (This is the reason for the name style). These functions aren’t too useful or interesting themselves, but when we get to expanding the logging family, they’ll make a lot more sense.
Expanding the return_if_Log family
With the above library of functions as our template, we can infer a wide library of Logging variations we may want. Like these:
“if_Log” is a nice one. Conditionally choose to print something in one command.
“return_Log” is interesting. In cases like the 2nd code sample where we want to return the value of the test, we can now write a report to the Log at the same time.
The rest should be self explanatory. The same above return_if calls with Logging support.
Now with a complete library of functions, we can make a subtle improvement to the above example code.
I am not sure I would actually encourage this, since the separate “return 0” at the end actually stands out better. The “return 0” is the true end of the function, and there’s no chance of accidentally placing a line after the return_Log call and not realizing the code is unreachable. That said, your compiler should still give you warning about the unreachable code, but better safe than sorry.
Assert and Warning
The return_log family of functions are a lot like the standard C function called assert. You or your project lead may have added an “AssertMsg” function to your library code, basically, an assert combined with a Logging print (or MessageBox).
Asserts are extra tests that only get included in your debug build (#ifndef ndebug). In your release build, they should not show up. If you have a popup message that’s still Assert grade that needs to ship in release code, then Assert is probably the wrong function and you need something else for this case (i.e. ErrorLog).
That about sums me up. In the past when I was a project lead, I’d often have an AssertMsg function. Over time, I decided that Assert should always have a message attached to it, so upper case “Assert” became my standard function call for asserting, and it always takes printf style arguments.
Asserts are unrecoverable errors. If you reach one, it should be theoretically impossible to continue. That said, some errors actually cause weird behavior instead outright crashing. It may still crash and burn right away after, but sometimes you have additional Assert-like checks that would be nice if they also triggered, so you had a more complete log of the failure.
Me, I have 2 functions: Assert, and Warning. Both work exactly the same, taking the test, printf style arguments, but Assert calls exit(1) causing the program to exit immediately. In practice I rarely use Assert, but Warning gets heavy use. Again, this is because I often have several lines of Warning messages, and knowing when I trigger multiple failure cases is helpful.
Assert and Warning are a lot like if_Log; They test, then on a non-zero value, they trigger. Since they work so similarly, it makes sense that they should become part of the return_if_Log family.
Notably, Assert never returns, so it doesn’t need any return_if variations. Warning does however.
Asserts and Warnings never don’t perform a test, so the syntax isn’t exactly consistent with Log. That said, I include both versions (with and without the word “if”) for completeness.
Bringing it all together
Here’s the real implementation of NewGLContext.
As opposed to the more verbose version.
return_if really is a subtle thing, but as functions grow due error checks, it’s a way to keep things a little cleaner.