Reputation: 11258
The scenario:
We have an API that has a generic error handling interface. It is a C API, so it tells us that after every API function called, we need to execute some boilerplate like this:
if (APIerrorHappened()) {
APIHandle h;
while(h = APIgetNextErrorContext(...)) {
cerr << APIgetText(h) << endl;
}
}
You obviously hate repeating yourself, so you want to encapsulate this handling in a macro that allows you to write code like this:
...
// this API call returns something
APItype foo = MYMACRO(APIsomeFunction1(...));
// this one doesn't
MYMACRO(APIsomeFunction2(...));
// neither does this (glCompileShader)
MYMACRO(APIsomeFunction3(...));
...
You can also think of this in terms of aspect-oriented programming - imagine that the macro adds logging, sends info to remote monitor, whatever... The point is that it is supposed to encapsulate an expression, do whatever around it, and return whatever type the expression returns - and of course the expression may not return anything.
Which means you can't just do
#define MYMACRO(x) { auto x = expr(); ... }
...because in some cases, the expression doesn't return anything!
So... How would you do that?
Please don't suggest encapsulating the complete statement inside the macro...
#define MYMACRO(x) \
{ \
/* ... stuff ... */ \
x; \
// ... stuff
}
...since this would never work for stuff like:
if (foo() || APIfunctionReturningBool(...) || bar()) {
...
APIfunction1();
...
} else if (APIOtherfunctionReturningBool() || baz()) {
...
APIfunction2();
...
}
...you engulf all the if statement? Its actions include other API calls, so... macro within macro? Debugging just became hell.
My own attempt is below, using lambdas and std::function - but it is arguably, ugly... I could not pass a lambda of the expression directly to a template that takes std::function (to specialize based on the lambda's return type), so the code turned out rather nasty.
Can you think of a better way?
void commonCode(const char *file, size_t lineno) {
// ... error handling boilerplate
// ... that reports file and lineno of error
}
template <class T>
auto MyAPIError(std::function<T()>&& t, const char *file, size_t lineno) -> decltype(t()) {
auto ret = t();
commonCode(file,lineno);
return ret;
}
template<>
void MyAPIError(std::function<void(void)>&& t, const char *file, size_t lineno) {
t();
commonCode(file,lineno);
}
template <class T>
auto helper (T&& t) -> std::function<decltype(t())()>
{
std::function<decltype(t())()> tmp = t;
return tmp;
}
#define APIERROR( expr ) \
return MyAPIError( helper( [&]() { return expr; } ), __FILE__, __LINE__);
UPDATE, an addendum to KennyTM's excellent solution
I placed the actual OpenGL code that triggered this question here. As you can see, the error checking code did more than just print - it also threw an exception that the user code could then handle. I am adding this addendum to note that with KennyTM's solution, you end up throwing this exception from a destructor, and that this is OK (read on):
struct ErrorChecker {
const char *file;
size_t lineno;
~ErrorChecker() {
GLenum err = glGetError();
if (err != GL_NO_ERROR) {
while (err != GL_NO_ERROR) {
std::cerr <<
"glError: " << (char *)gluErrorString(err) <<
" (" << file << ":" << lineno << ")" << std::endl;
err = glGetError();
}
throw "Failure in GLSL...";
}
}
};
The reason it is OK to throw from this destructor, is explained in the C++ FAQ:
The C++ rule is that you must never throw an exception from a destructor that is being called during the "stack unwinding" process of another exception... you can say never throw an exception from a destructor while processing another exception.
In our case, we want the user code (that calls the special macro) to handle the exception; so we need to know for certain that our "throw" in the ErrorChecker's destructor is the first one - i.e. that the actual C API called can never throw. This is easily accomplished with this form:
#define GLERROR(...) \
([&]() -> decltype(__VA_ARGS__) \
{ \
ErrorChecker _api_checker {__FILE__, __LINE__}; \
(void) _api_checker; \
try { \
return __VA_ARGS__; \
} catch(...) {} \
} ())
This form of the macro guarantees that the actual C API (called via VA_ARGS) will never throw - and therefore, the "throw" in ErrorChecker's destructor will always be the first one doing it.
So this solution covers all angles of my original question - many thanks to Alexander Turner for providing it.
Upvotes: 2
Views: 726
Reputation: 523334
Put the logging code to a destructor of some class — assuming the logger won't throw — and then create an instance of that class in the macro. Abusing the comma operator, we have:
struct Logger
{
const char* file;
int lineno;
~Logger()
{
// The APIerrorHappened stuff.
}
};
#define APIERROR(...) (Logger{__FILE__, __LINE__}, (__VA_ARGS__))
Demo: http://ideone.com/CiLGR
Upvotes: 2