3

Is it possible to replace this preprocessor macro:

#define AL_CALL(a) do { a;                              \
                        ALenum e = alGetError();        \
                        if(e != AL_NO_ERROR)            \
                            UtilitySoundNode::printALError(e,__FILE__, __LINE__); \
                       } while(0)

with a C++ template? If it is possible, will make any sense to do it (pros/cons - overhead/debugging)?

Note: Basically I am wondering if there is an elegant way to handle this kind of error handling in C++.

EDIT: Of course I made a mistake a is a function call. As one may guess it is a function call with parameters of a OpenAL function.

AL_CALL(someAlFunction(param1, param2))

NOTE: Somebody decided to edit the macro and make it nicer but I'd prefer to keep the original one too. So here it is:

#define AL_CALL(a) {a; ALenum e = alGetError();if(e != AL_NO_ERROR)PUtilitySoundNode::printALError(e,__FILE__, __LINE__);}
5
  • What type can a be? Commented Dec 19, 2015 at 17:35
  • What could a be that would make the statement a; make any sense? It is hard t answer this question without knowing that. Perhaps you mean a(); and it is a function pointer? If that is the case then a simple inline-functon is all that is needed, unless you need functons with different signatures. Commented Dec 19, 2015 at 17:38
  • Is that really a c macro? (UtilitySoundNode::printALError!) Commented Dec 19, 2015 at 17:42
  • 1
    @user3490458 : I guess he means a C-Preprocessor macro - you can put anything you like in a macro, as far as the preprocessor is concerned, the preprocessor does not check syntax, that is done by the compiler after expansion. I edited the text and tags to correct that. Commented Dec 19, 2015 at 17:45
  • @AnonMail It is a function call. I am sorry for failing to point this out. Commented Dec 20, 2015 at 9:54

4 Answers 4

4

One problem here seems to be that the "a" can be some arbitrary function (with parameters) which sets the error code returned by alGetError().

That can be rewritten to C++ by using a functor object. To pass the arguments (and object instance if necessary) std::bind or boost::bind can be used (note that to bind reference args the std::ref/boost::ref is necessary).

However, if you'd want to still have the __FILE__ and __LINE__ passed the the printError() that C++ template still would need to be called by a macro which will pass those to the template. __FILE__ and __LINE__ are only expanded by the preprocessor, so there is no way around using a macro for them.

But the macro could be much simpler then and most of the work can be done in the C++ template (which has many advantages e.g. for debugging, because in most debuggers you cannot step into a macro).

EDIT: adding the code as an example:

template<typename T>
void ALcallAndCheck(T c, const char *file, size_t line)
{
    c();
    ALenum e = alGetError();
    if(e != AL_NO_ERROR)
        UtilitySoundNode::printALError(e, file, line); \
}

#define AL_CALL(a) ALcallAndCheck(a, __FILE__, __LINE__)

Then, instead of

AL_CALL(SomeFunction(2, refAttr));

the call will become:

AL_CALL(std::bind(SomeFunction, 2, std::ref(refAttr)));

EDIT 2: The previous indeed does not work with expressions, which the original macro allows. To work also for expressions, the macro can be altered to:

#define AL_CALL(a) ALcallAndCheck([&]{ (a); }, __FILE__, __LINE__)

That will create a lambda which will evaluate anything that comes into the macro. Then even the std::bind is not necessary and it can be called directly as:

AL_CALL(SomeFunction(2, refAttr));
AL_CALL(SomeOtherFunction1()+SomeOtherFunction2(8));
Sign up to request clarification or add additional context in comments.

3 Comments

Nice that you bring up std::bind, (this is not a comment on my upvote).
Does std::bind create any overhead? Would you use it (std::bind) and that template in a performance critical code (e.g. a render loop)? As an alternative I am starting to think about an error handling with a goto (although I am not quite sure if it will work).
@axalis Unfortunately I'll need to pass any C++11 features or boost.
2

No, the use of __FILE__ and __LINE__ pretty well require the preprocessor.

Comments

1

Note that using a template instead of a macro does not produce an exact analog. The macro defined in your question allows a to represent a statement as well as an expression. A template does not have that kind of flexibility. The template defined below assumes a is a non-void expression.

There is no standard way to implicitly inject a function caller's file name and line number without the caller passing in that information to the called function. A preprocessor macro allows a means to make the syntax appear to be implicit injection, when in fact the information is being passed.

template <typename T>
void AL_CALL (T a, const char *file, int line) {
    ALenum e = alGetError();
    if(e != AL_NO_ERROR)
        UtilitySoundNode::printALError(e, file, line);
}

#define AL_CALL(X) AL_CALL((X), __FILE__, __LINE__)

You may be able to use system specific facilities (e.g., CaptureStackBackTrace + SymFromAddr or backtrace + backtrace_symbols) to get approximately the same information implicitly, but it may require debugging symbols to be present, and inline functions may not produce the expected output.

3 Comments

This does not seem to work if the function passed as the X parameter does return void ("could not deduce template argument for 'T' from 'void'") - that's why I was using std::bind() in my response.
@E.Maskovsky: My answer already stated my assumptions. Your solution still does not resolve the general problem of the original macro allowing the argument to be a statement (e.g., an if statement).
Oh, sorry, didn't notice that. And yes, the macro also allows expressions, which the std::bind solution does not allow (but that could be perhaps solved by using lambda in the support macro)
0
template<class A>
void al_call(A&&a){
  ALenum e = a();
  if(e != AL_NO_ERROR)
    UtilitySoundNode::printALError(e,__FILE__, __LINE__);
}

Use:

al_call( [&]{ return bob(); });

Instead of:

AL_CALL( bob() )

The line/file info is not useful above.

So

template<class A>
void al_call(A&&a, char const*file, unsigned line){
  ALenum e = a();
  if(e != AL_NO_ERROR)
    UtilitySoundNode::printALError(e,file, line);
}
#define AL_CALL(...) al_call([&]()mutable{return __VA_ARGS__;}, __FILE__, __LINE__)

and it is almost a drop in replacement.

2 Comments

(of course meant expanded to the correct values, they will indeed be expanded, but AFAIK not to the caller file and line)
@Yakk, Unfortunately it turn's out I'll have to support GCC 3.x.x so Lambdas and such modern stuff are out of question. So is there any solution that doesn't require C++11 or boost?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.