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:
keywords
identifiers with special meaning (list on https://en.cppreference.com/w/cpp/keyword)
standard attribute tokens (list on https://en.cppreference.com/w/cpp/language/attributes#Standard_attributes)
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 formYYYYMM
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 value1
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 uglyMmm 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 formathh: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; } |