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
- 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
(macro, ...) OVERLOAD
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("x");
COUT("x", 2);
COUT("x", 2, 'z');
COUTreturn 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
(major) <1>
VERSION_STRING(major, minor) <2>
VERSION_STRING(major, minor, patch) <3> VERSION_STRING
- Expands to “major”.
- Expands to “major.minor”.
- 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"
.
Print Code Lines and Results
(code) <1>
RUN(code, out) <2>
RUN(code, out1, out2) <3> RUN
- Prints a line of code to the screen and then runs it.
- Prints a line of code to the screen, runs it, then prints the value from
out
. - Prints a line of code to the screen, runs it, then prints the values
out1
andout2
.
This overloaded macro prints what will be executed in a test program, optionally followed by some results. Best illustrated with an example.
Example
#include <utilities/macros.h>
#include <format>
#include <iostream>
#include <iterator>
#include <string>
int main()
{
(std::string s1);
RUN(double x = 123456789.123456789);
RUN(std::format_to(std::back_inserter(s1), "x = {:12.5f}", x), s1);
RUN}
Output
[CODE] std::string s1
[CODE] double x = 123456789.123456789
[CODE] std::format_to(std::back_inserter(s1), "x = {:12.5f}", x)
[RESULT] s1: x = 123456789.12346