9

As far as I know, macros rearrange the program text before the compiler even sees it properly, thus potentially causing problems. I hardly ever see them in C++ code, mostly in C.

The only good use I know of, is inclusion guards (#ifndef).

Is there anything else that needs to be done with macros and cannot be implemented in a cleaner way?

Oleksiy
  • 37,477
  • 22
  • 74
  • 122

11 Answers11

4

Prior to C++11, you would usually define static_assert as a macro (a typedef that would be invalid if the condition is false), so it can be available from anywhere (namespace level, or function level) and still not be ambiguous (e.g. by using a line number).

The Boost.Preprocessor library is another good example of using macros to reduce the amount of highly redundant code, and another one that is less relevant with variadic templates.

Furthermore macros are widely used to "talk" to the compiler, e.g. checking what compiler you are running on, what version of the compiler, whether C++11 support is available, etc.

nikolas
  • 8,707
  • 9
  • 50
  • 70
4

Logging and Exception.

A macro allows you to effortlessly capture __FILE__, __LINE__ and __func__. Oh sure you could write them manually each time, but frankly this is tedious and error prone (both __FILE__ and __func__ are C-string so you risk mixing them up).

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
3

Yes, the X-macro trick will always be useful.

You put your data in one header (no #include guards!), then use a macro to conditionally expand it.

Example:

Data.h:

X(Option1, "Description of option 1", int, long)
X(Option2, "Description of option 2", double, short)
X(Option3, "Description of option 3", wchar_t*, char *)

MyProgram.cpp:

enum Options
{
#define X(Option, Description, Arg1, Arg2) Option,
#   include "Data.h"
#undef X
};

char const *descriptions[] =
{
#define X(Option, Description, Arg1, Arg2) Description,
#   include "Data.h"
#undef X
};

#define X(Option, Description, Arg1, Arg2) typedef void (*P##Option)(Arg1, Arg2);
#   include "Data.h"
#undef X

It's not the prettiest sight, but it avoids code duplication and lets you keep everything in one place.

Community
  • 1
  • 1
user541686
  • 205,094
  • 128
  • 528
  • 886
2

Macros can be defined from the compiler command line, with -DFOO, which would define the macro FOO. This is most often used for conditional compilation, e.g. a certain optimization that is known to work on some platforms but not on others. The build system can detect whether the optimization is viable an enable it using this kind of macro.

This is one of the few uses of macros that I believe can be used well. However, it's of course possible to abuse this feature as well.

arne
  • 4,514
  • 1
  • 28
  • 47
2

The macros are considered error prone due to its characteristics, here we have some good examples of error prone macros:

But they could be useful in some ways, for example, in order to make te code more readable when dealing with function pointers:

class Fred {
public:
    int f(char x, float y);
    int g(char x, float y);
    int h(char x, float y);
    int i(char x, float y);
    // ...
};

// FredMemberFn points to a member of Fred that takes (char,float)
typedef  int (Fred::*FredMemberFn)(char x, float y);

// Useful macro:
#define callMemberFunction(object,ptrToMember)  ((object).*(ptrToMember))

With the "Useful macro", you can call function pointers like this:

callMemberFunction(fred,memFn)('x', 3.14);

That is slightly more clear than:

(fred.*memFn)('x', 3.14);

Credits: C++ FAQ Lite.

PaperBirdMaster
  • 12,806
  • 9
  • 48
  • 94
  • 1
    I would actually say this is a bad example. It's slightly more clear **to you**, however any C++ educated programmer knows `.*`, whereas you just created **a dialect**. When I see `callMemberFunction` my first instinct is to check the implementation to make sure it does what it says. – Matthieu M. Aug 22 '13 at 14:09
2

Several uses (some may have been mentioned already..)

  1. Logging: DEBUG(....), this is neat because the contents are only evaluated if the logging is active (so the macro could have a test for log level for example...) You cannot replace this with an inline function as the arguments will always be evaluated. However with c++11, there is a lambda trick which will avoid the evaluation, however the syntnax is cludgy so you'll end requiring a macro anyway to clean it up! :)
  2. Code generation, I use lots of SFINAE tests, and it's easy to generate the test with a couple of macros rather than hand construct the test every time.
Nim
  • 33,299
  • 2
  • 62
  • 101
1

There are some code rewriting for optimization that seem not to be doable with template metaprogramming and need macros. Here is a probable example: C++ template for unrolling a loop using a switch?

Community
  • 1
  • 1
hivert
  • 10,579
  • 3
  • 31
  • 56
  • That’s definitely feasible with templates though. The SeqAn library uses templates for loop unrolling in their implementation of the hashing for gapped q-grams. – Konrad Rudolph Aug 22 '13 at 07:17
  • @KonradRudolph If you think it is doable, I would be very happy if you answer the linked question ? I'm pretty sure it is not doable for this particular problem because compile time/runtime/switch constraints. – hivert Aug 22 '13 at 07:20
1

Yes, they will still have uses such as for "message maps" in ATL, WTL, MFC.

For example, this is part of some personal code I have:

BEGIN_MSG_MAP(This)
    MESSAGE_HANDLER(WM_CLOSE, OnClose)
    MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
    MESSAGE_HANDLER(WM_GETDLGCODE, OnGetDlgCode)
    COMMAND_RANGE_HANDLER(IDOK, IDNO, OnButtonClick)
    CHAIN_MSG_MAP(CDialogResize<This>)
END_MSG_MAP()

Or even specifying the layout of a window:

BEGIN_DLGRESIZE_MAP(This)
    DLGRESIZE_CONTROL(IDOK, DLSZ_MOVE_X)
    DLGRESIZE_CONTROL(IDCANCEL, DLSZ_MOVE_X)
    DLGRESIZE_CONTROL(IDC_EDITINPUT, DLSZ_SIZE_X)
END_DLGRESIZE_MAP()

Writing this without macros would involve a lot of unnecessary boilerplate code.

user541686
  • 205,094
  • 128
  • 528
  • 886
1

When you need to utilize platform, compiler, or implementation specific features. Typically, this is either to improve portability or to access features which are expressed differently in the systems you target (i.e. it may be written differently on the compilers you use).

And expanding on Matthieu's answer (+1): Assertions.

justin
  • 104,054
  • 14
  • 179
  • 226
1

Writing exception-checking tests with macros is much easier than with functions.

#define TEST_THROWS(code) do { try { code; } catch (...) { pass(); } fail(); } while(0)

Note: example not tested

OrangeDog
  • 36,653
  • 12
  • 122
  • 207
1

Extending on the answer from @Matthieu, I've used macros to add file and line logging to a legacy codebase. So this:

void MovePlayer(Vector3 position)
{ 
    ...
}

became something like:

#define MovePlayer(pos) MovePlayer_(pos, __FILE__, __LINE__)

void MovePlayer_(Vector3 position, const char* file, int line)
{
    LogFunctionCall("MovePlayer", file, line);
    ...
}

By only changing one place in the codebase, I was able to log everywhere that the function got called during a complex test. If you do this to enough functions, it's very useful for tracing existing behaviour in old codebases.

Blobinator
  • 358
  • 1
  • 8