02 - object-like macros

The #define directive offers text replacement and is commonly used as a building block for more complex preprocessor code.

Syntax

1
2
3
4
5
6
7
8
9
// object-like macros
#define identifier replacement-list
// function-like macros
#define identifier(parameters) replacement-list
#define identifier(parameters, ...) replacement-list
#define identifier(...) replacement-list

// undefine any macro
#undef identifier

The replacement-list is always optional (a macro may have "empty" definition and just exist). The identifier can be anything except:

In practice, implementations allow such macros (preprocessor doesn't understand C++ grammar and keywords after all) but output a warning - the standard doesn't want programmers to hijack core parts of the language by the preprocessor text replacement mechanism.

In the example below you can see both object-like and function-like macros in action. The preprocessor is capable of basic math operations - these are typically used to check the level of available features.

1
2
3
4
5
6
7
8
9
10
11
12
13
// inside the library
#define LIB_VERSION_ENCODE(x, y, z) (10000 * x + 100 * y + z)
#define LIB_VERSION LIB_VERSION_ENCODE(2, 1, 3)

// some code that uses the library
#include <lib/version.hpp>
#if LIB_VERSION >= LIB_VERSION_ENCODE(2, 0, 0)
	// [...] code that uses newer approach
#elif LIB_VERSION >= LIB_VERSION_ENCODE(1, 6, 11)
	// [...] code that uses older approach
#else
	#error LIB_VERSION is too low!
#endif

#undef

Generally, there is no need to undefine previously defined macros - each translation unit that is a part of compilation process (source file + all files it includes) will run through separate preprocessor. Still, in some cases undefining may be desirable to affect other directives.

Unlike the C++ compiler (when it comes to duplicate definitions), the preprocessor reacts differently to conflicting directives:

  • #define: if the identifier is already defined as any type of macro, the program is ill-formed unless the definitions are identical.

  • #undef: if the identifier does not have associated macro, the directive is ignored.

Still, major compilers in both cases (duplicate definition, undefining non-existing macro) will issue warnings as such code while technically valid, typically indicates some mistake within preprocessor code.

When macro presence can not be easily predicted (which is the case on many platforms where supported features vary) one can prepend operations with checks to avoid warnings:

1
2
3
4
5
6
7
#ifndef ABC
#define ABC 1
#endif

#ifdef XYZ
#undef XYZ
#endif

Predefined macros

Several macros are defined by the C++ standard. You don't need to include any files to get them.

Macro constants

Most notable ones:

  • __cplusplus - version of the C++ standard being used as a number in the form YYYYMM

    • C++98: 199711L

    • C++11: 201103L

    • C++14: 201402L

    • C++17: 201703L

    • C++20: 202002L

  • __STDC_HOSTED__ - 1 if the implementation runs on an OS, 0 if freestanding (the code is the operating system)

  • __STDCPP_THREADS__ - if present and if has value 1 indicates that the implementation supports multiple threads of execution

More listed on https://en.cppreference.com/w/cpp/preprocessor/replace

Feature-testing macros

These macros indicate support for certain language features or certain standard library parts. Even though __has_include and __has_cpp_attribute are officially since C++17 and C++20 respectively, GCC and Clang supported them much earlier - if I'm right it was probably compiler implementers idea that later turned out to be so useful it has been standarized. With the help of these macros you can enable a better implementation of something at compile time and still have working code if the feature is not supported.

Full list on https://en.cppreference.com/w/cpp/feature_test

Magic macros

There are also few "magic" macros which have very unique behavior:

  • __FILE__ - expands to an implementation-defined string literal indicating currently processed file. IMO GCC made a very good decision to simply output the same string as the file path provided to the compiler - so whether it's relative or absolute the form will be preserved.

  • __LINE__ - expands to an integer constant indicating current line number.

  • __DATE__ - expands to a string literal indicating date of preprocessing the file. Has the ugly Mmm dd yyyy format - ugly because month is not a number and there is no order consistency like year/month/day in ISO 8601 format.

  • __TIME__ - expands to a string literal indicating time of preprocessing the file in the format hh:mm:ss.

These are commonly used for testing, debugging (especially assertions) and logging. Note that __DATE__ and __TIME__ expand during preprocessing, not during runtime. Once a file with these is compiled it will contain the same value until a file containing the macro is recompiled again.

__func__

In the scope of every function body, there is a special function-local predefined variable named __func__, defined as a static character array holding the name of the function in implementation-defined format. It's not a preprocessor directive because the preprocessor does not understand C and C++ code - it sees everything as text and doesn't understand what a function is. For this reason __func__ has been specified as magic function-local variable. This also means that directives like #ifdef __func__ will not work.

The value is implementation defined, but on all major implementations it's simply the name of the function. If you want a richer variant (enclosing scopes + arguments + cv-qualifiers etc.), <boost/current_function.hpp> offers BOOST_PRETTY_FUNCTION macro which tries to detect the compiler and use an alternative if the implementation supports it. GCC and Clang offer __PRETTY_FUNCTION__ (magic variable) and MSVC offers __FUNCSIG__ (macro - no idea why/how).

For extra information, see SO: What's the difference between __PRETTY_FUNCTION__, __FUNCTION__, __func__?.

Implementation macros

Compilers provide a lot of information about the implementation in their own macros (not defined by the standard). Many are very useful for writing multiplatform code or code that takes advantage of certain features available only under special conditions. They typically provide information such as compiler name and version, target architecture and operating system, min/max/size/alignment of built-in types, endianess and other details.

For GCC and Clang, you can dump all of the predefined macros using a command like echo | g++ -x c++ -std=c++17 -dM -E - | sort (should work on Windows and many GNU/Linux distributions). See https://stackoverflow.com/q/2224334/4818802 for more info.

NDEBUG

One particular macro that is very often used is NDEBUG. It's presence signifies a release build and based on it, a lot of other macros may be defined differently to enable/disable/alter certain functionality that should be present only in debug or release builds. This typically includes assertions (explained later) and logging.

The macro name is reversed ("not debug", present only in release builds) for historical reasons.

Constants

In both C and C++ various data must be a constant expression, e.g. an array size. C++ has extended const functionality so that some const-qualified objects could also be used for such purposes (this was before C++11 introduced constexpr) (the rules are somewhat complex so in C++11 and later code I strongly recommend constexpr).

In C however, const is not as powerful. This creates a necessity in C to use macros, otherwise the constants can not be used when a constant expression is needed:

1
2
3
4
5
6
7
8
9
10
11
// C approach
#define FOOBAR_SIZE 1024
char foobar[FOOBAR_SIZE]; // ok: size is a constant expression
for (int i = 0; i < FOOBAR_SIZE; ++i)
	// ... something with foobar[i]

// C++ approach (after C++11 would use constexpr)
const int foobar_size = 1024; // in C not treated as constant expression
char foobar[foobar_size]; // this would not be valid in C
for (int i = 0; i < foobar_size; ++i)
	// ... something with foobar[i]

In C using such macros is simply a necessity, otherwise there is no way to deal with code duplication. The language simply has no feature that solves this problem so the preprocessor text replacement is the only choice. In C++ the situation is much different so the following things are considered bad habits in C++:

  • The use of macros when they are not needed. C++ has more powerful keywords and when macros can be avoided, they should be avoided.

  • Naming constants using UPPERCASE when they are not macros. This may cause confusion and make regular code vulnerable to a macro of the same name. This is also mentioned in ES.9, NL.9 and Enum.5 points in Core Guidelines.

Exercise

Write a program that outputs Hello from C++XX where XX are last 2 digits of the year of the standard used.

solution

A solution with #if for each value works too but here is the most clever one:

1
2
3
4
5
6
#include <iostream>

int main()
{
	std::cout << "Hello from C++" << __cplusplus / 100 % 100;
}