Sunday, September 29, 2013

The proper way to write multi-statement macros in C++

If you've been programming for a while, you've probably written some multi-statement macros, and you probably wrap them in {} brackets like this:

#define print_words(str1, str2) { \
    cout << str1 << " ";          \
    cout << str2;                 \
}

int main() {
    print_words("hello", "world");
    return 0;
}

This looks fine at first glance, and the program will print out "hello world" as expected... So whats the problem?

The problem is calling this macro in certain places of code will generate errors that calling a normal function wouldn't.

For example:

#define print_words(str1, str2) { \
    cout << str1 << " ";          \
    cout << str2;                 \
}

int main() {
    if (1) print_words("hello", "world");
    else   cout << "good bye";
    return 0;
}

If you try to compile this, you will get a compilation error.
GCC gives the following: "error: 'else' without a previous 'if'"

Why do we get this error? Its because the generated code with the macro expanded looks like this:

int main() {
    if (1) { cout << "hello" << " "; cout << "world"; };
    else   cout << "good bye";
    return 0;
}

In c++ if you have an if-statement that uses brackets, and then you terminate the bracket using a semi-colon, it will terminate the whole if-statement, such that if you use an "else" clause after it, the compiler won't have an "if" to match it up with. So code that looks like: "if (1) {}; else {}" will have a compilation error. The correct code should not have a semi-colon: "if (1) {} else {}"

So if we want to write a multi-statement macro correctly (so it doesn't give us these errors), we need to use a trick when writing the macro. The common way to solve this is instead of wrapping the macro in brackets, we wrap the macro in a "do {} while(0)" loop like this:

#define print_words(str1, str2) do { \
    cout << str1 << " ";             \
    cout << str2;                    \
} while(0)

int main() {
    if (1) print_words("hello", "world");
    else   cout << "good bye";
    return 0;
}

Now when the macro is expanded, the semi-colon terminator will terminate the do-while loop, instead of terminating the if-statement: "if (1) do { } while(0); else {}", and this works fine without compilation errors.

So now you know the secret to writing robust macros. Wrap them in do-while loops!
This trick is also a great way to distinguish veteran c/c++ coders from novices. Most people will not know why the do-while loop is necessary; whereas veterans have likely seen this trick used before.
I recommend to just always wrap your multi-statement macros in a do-while loop.

p.s. There are other ways to solve this problem. One of them is to use the comma-operator to group all the expressions as one big statement. But it is not applicable in all cases (since it doesn't let you declare new variables inside the macro), and I won't go over it in this article.

5 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. Perhaps it should be noted that the extra do-while loop in this case would not cause runtime overhead since the compiler should see the loop portion as useless and automatically remove it altogether.

    ReplyDelete
  3. It's better replacing it with a function rather than a macro in the assumed scenario,

    ReplyDelete
    Replies
    1. But sometimes its very useful. Consider a case when you want to develop a FSM.

      Delete