Macros

Introduction

The header <utilities/macros.h> supplies some useful macros.

These include the OVERLOAD macro, which implements a well-known trick to “overload” any macro based on the number of passed arguments. See overloading below.

Microsoft’s old traditional preprocessor is unhappy with OVERLOAD, but their newer cross-platform compatible one is fine. Add the /Zc:preprocessor flag to use that upgrade at compile time. Our CMake module compiler_init does that automatically for you.

Compiler String

COMPILER_NAME

Expands to a string that encodes the name of the compiler and its version. Test code can use the macro to annotate results. We only support the usual three compiler suspects, gcc, clang, and MSVC, but adding more compilers is not difficult.

One of the examples below uses this macro.

Macro Expansion

1STRINGIZE(foo)
2CONCAT(foo, bar)
1
Turn the argument into a string.
2
Concatenates the two arguments.

These two classic macros work even if the arguments you pass in are themselves macros by fully expanding all arguments.

Overloading

OVERLOAD(macro, ...)

You can “overload” a macro based on the number of passed arguments.

For example, if you have overloaded FOO macro:

#define FOO(...) OVERLOAD(FOO, __VA_ARGS__)

The consumer of the macro can call it as FOO(), or FOO(a), or FOO(a,b).

All calls get automatically forwarded to the correct concrete macro. For FOO, these must be named FOO0, FOO1, FOO2, etc. The macro writer provides whichever of these makes sense, but the macro’s consumer can use them through the more straightforward call.

Example

#include <utilities/macros.h>
#include <iostream>

1#define COUT(...) OVERLOAD(COUT, __VA_ARGS__)

2#define COUT0(x)       std::cout << "Zero argument version:   " << '\n'
#define COUT1(x)       std::cout << "One argument version:    " << x << '\n'
#define COUT2(x, y)    std::cout << "Two arguments version:   " << x << ", " << y << '\n'
#define COUT3(x, y, z) std::cout << "Three arguments version: " << x << ", " << y << ", " << z << '\n'

int main()
{
    std::cout << "Compiler: " << COMPILER_NAME << '\n';

    // NOTE: We only ever call COUT, but we expect to get the correct concrete version.
    COUT();
    COUT("x");
    COUT("x", 2);
    COUT("x", 2, 'z');
    return 0;
}
1
We overload a trivial macro depending on the number of passed arguments.
2
The one, two, and three argument versions of COUT.

Output

Compiler: clang 17.0.6
Zero argument version:
One argument version:    x
Two arguments version:   x, 2
Three arguments version: x, 2, z

Semantic Version Strings

VERSION_STRING(major)                   <1>
VERSION_STRING(major, minor)            <2>
VERSION_STRING(major, minor, patch)     <3>
  1. Expands to “major”.
  2. Expands to “major.minor”.
  3. Expands to “major.minor.patch”.

VERSION_STRING is an overloaded macro. For example, VERSION_STRING(3, 1, 0) expands to the string "3.1.0".

Back to top