The C++ preprocessor is a text replacement tool used to transform the source code in order to produce a single text file that is then passed to the actual compiler. It has various capabilities, such as including files, conditional compilation, text macro replacement, error emitting, stringizing, or token concatenation. Often developers use the preprocessor when other alternatives are available and are more appropriate. In this article, I will show five examples of when and how you can avoid the use of the preprocessor.
Table of contents:
- Object-like macros
- Function-like macros
- Exception handling macros
- Conditional compilation
- Policy-like macros
- Bonus: including files
Object-like macros
These are identifiers that are replaced with a fragment of code and are often used to give symbolic names to numerical or string literals. Here is a typical example you must have seen many times.
#define BUFFER_SIZE 1024 int main() { char buffer[BUFFER_SIZE]; }
Instead of being a macro, BUFFER_SIZE could, and should, be defined as a compile-time constant.
constexpr size_t BUFFER_SIZE = 1024;
Notice it is declared as constexpr and not just const. The latter indicates a value that does not change, but might only be available at runtime. The former, implies constness, but is guaranteed to be available at compile-time. constexpr values can be used in any place where compile-time constants are expected.
Many times object-like macros are used to define related symbolic names, such as in the following example:
#define PERMISSION_NONE 0 #define PERMISSION_READ 1 #define PERMISSION_WRITE 2 #define PERMISSION_ADD 4 #define PERMISSION_DELETE 8 void show_permissions(int const p) { if(p & PERMISSION_READ) std::cout << "can read" << std::endl; if (p & PERMISSION_WRITE) std::cout << "can write" << std::endl; if (p & PERMISSION_ADD) std::cout << "can add" << std::endl; if (p & PERMISSION_DELETE) std::cout << "can delete" << std::endl; } int main() { int flags = PERMISSION_READ | PERMISSION_WRITE; show_permissions(flags); flags |= PERMISSION_DELETE | PERMISSION_ADD; flags &= ~PERMISSION_WRITE; show_permissions(flags); }
Following the previous example, we can simply replace these with constexpr values (in a class or namespace scope):
constexpr int PERMISSION_NONE = 0; constexpr int PERMISSION_READ = 1; constexpr int PERMISSION_WRITE = 2; constexpr int PERMISSION_ADD = 4; constexpr int PERMISSION_DELETE = 8;
However, these macros, representing bitflags here, can also be replaced with an enumerator.
enum class permissions { none = 0, read = 1, write = 2, add = 4, del = 8 }; void show_permissions(int const p) { if(p & static_cast<int>(permissions::read)) std::cout << "can read" << std::endl; if (p & static_cast<int>(permissions::write)) std::cout << "can write" << std::endl; if (p & static_cast<int>(permissions::add)) std::cout << "can add" << std::endl; if (p & static_cast<int>(permissions::del)) std::cout << "can delete" << std::endl; } int main() { int flags = static_cast<int>(permissions::read) | static_cast<int>(permissions::write); show_permissions(flags); flags |= static_cast<int>(permissions::del) | static_cast<int>(permissions::add); flags &= ~static_cast<int>(permissions::write); show_permissions(flags); }
This code is more verbose than the original one and you might be tempted to avoid writing all these explicit casts. You can actually make it as simple as the original and avoid macros, by overloading various operators for the enumerator type. The following snippet shows the completely rewritten example.
enum class permissions { none = 0, read = 1, write = 2, add = 4, del = 8 }; inline int operator |(permissions const lhv, permissions const rhv) { return static_cast<int>(lhv) | static_cast<int>(rhv); } inline int operator &(permissions const lhv, permissions const rhv) { return static_cast<int>(lhv) & static_cast<int>(rhv); } inline int operator |(int const v, permissions const p) { return v | static_cast<int>(p); } inline int operator |(permissions const p, int const v) { return v | static_cast<int>(p); } inline int operator &(int const v, permissions const p) { return v & static_cast<int>(p); } inline int operator &(permissions const p, int const v) { return v & static_cast<int>(p); } inline int operator~(permissions const p) { return ~static_cast<int>(p); } inline bool operator==(int const v, permissions const p) { return v == static_cast<int>(p); } inline bool operator==(permissions const p, int const v) { return v == p; } void show_permissions(int const p) { if(p & permissions::read) std::cout << "can read" << std::endl; if (p & permissions::write) std::cout << "can write" << std::endl; if (p & permissions::add) std::cout << "can add" << std::endl; if (p & permissions::del) std::cout << "can delete" << std::endl; } int main() { int flags = permissions::read | permissions::write; show_permissions(flags); flags |= permissions::del | permissions::add; flags &= ~permissions::write; show_permissions(flags); }
Function-like macros
These are macros that look like functions. The macro name is followed by none, one, or more arguments in paranthesis. Most of the times these can be replaced with regular functions or function templates. Let us get back to the previous example with the permission bigflag macros and introduce a couple function-like macros for setting and testing bit flag values.
#define PERMISSION_NONE 0 #define PERMISSION_READ 1 #define PERMISSION_WRITE 2 #define PERMISSION_ADD 4 #define PERMISSION_DELETE 8 #define SETBIT(fFlag, lValue, lBits) (fFlag ? (lValue) | (lBits) : (lValue) & (~lBits)) #define TESTBIT(lValue, lBits) (((lValue) & (lBits)) == (lBits)) void show_permissions(int const p) { if (TESTBIT(p, PERMISSION_READ)) std::cout << "can read" << std::endl; if (TESTBIT(p, PERMISSION_WRITE)) std::cout << "can write" << std::endl; if (TESTBIT(p, PERMISSION_ADD)) std::cout << "can add" << std::endl; if (TESTBIT(p, PERMISSION_DELETE)) std::cout << "can delete" << std::endl; } int main() { int flags = PERMISSION_READ | PERMISSION_WRITE; show_permissions(flags); flags = SETBIT(true, flags, PERMISSION_DELETE); flags = SETBIT(true, flags, PERMISSION_ADD); flags = SETBIT(false, flags, PERMISSION_WRITE); show_permissions(flags); }
The SETBIT and TESTBIT macros can be replaced with inline functions (SETBIT being replaced by two functions, one that sets a bit and one that resets a bit). For the following example, I assume the permissions scoped enum and the overloaded operators are defined as above.
inline int set_bit(int const v, permissions const p) { return v | p; } inline int reset_bit(int const v, permissions const p) { return v & ~p; } inline bool test_bit(int const v, permissions const p) { return (v & p) == p; } void show_permissions(int const p) { if(test_bit(p, permissions::read)) std::cout << "can read" << std::endl; if (test_bit(p, permissions::write)) std::cout << "can write" << std::endl; if (test_bit(p, permissions::add)) std::cout << "can add" << std::endl; if (test_bit(p, permissions::del)) std::cout << "can delete" << std::endl; } int main() { int flags = permissions::read | permissions::write; show_permissions(flags); flags= set_bit(flags, permissions::del); flags = set_bit(flags, permissions::add); flags = reset_bit(flags, permissions::write); show_permissions(flags); }
Exception handling macros
Confronted with repeated situations when they have to catch the same exeptions and handle them the same way, some developers resort to macros for avoiding repetitive code. The following is such an example.
#define TRACE_ERR(x) std::cerr << x << std::endl #define TRY_CALL try #define CATCH_CALL catch(std::runtime_error const & e) \ {\ TRACE_ERR(std::string("runtime error: ") + e.what());\ }\ catch (std::logic_error const & e) \ {\ TRACE_ERR(std::string("logic error: ") + e.what());\ }\ catch (std::exception const & e) \ {\ TRACE_ERR(std::string("exception: ") + e.what());\ }\ catch (...)\ {\ TRACE_ERR("unexpected error");\ } void func_that_throws() { throw std::runtime_error("an error has occurred!"); } int main() { TRY_CALL { func_that_throws(); } CATCH_CALL }
If you execute this program it will print runtime error: an error has occurred!. However, these macros are not debugable, and in practice may even be hard to write. This example can be rewritten to use a regular function as handler for multiple exceptions. The only difference in the main() function is an additional call for this function, error_handler().
inline void trace_error(std::string_view text) { std::cerr << text << std::endl; } void error_handler() { using std::string_literals; try { throw; } catch (std::runtime_error const & e) { trace_error("runtime error: "s + e.what()); } catch (std::logic_error const & e) { trace_error("logic error: "s + e.what()); } catch (std::exception const & e) { trace_error("exception: "s + e.what()); } catch (...) { trace_error("unexpected error"); } } void func_that_throws() { throw std::runtime_error("an error has occurred!"); } int main() { try { func_that_throws(); } catch(...) { error_handler(); } }
The throw; statement without an expression rethrows the currently handled exception. (It is allowed only when an exception is being handled, otherwise std::terminate() will be called). It can be used to create handlers that can catch and handle multiple exception types without the need to duplicate code or resort to macros.
Conditional compilation
This is a feature of the preprocessor that selects whether to include or not a chunk of code in the final text file that will be passed to the compiler. Preprocessor conditional directives can check arithmetic expressions or whether a name is defined as a macro.
In the following example, a message is written to the standard output stream when the program is compiled using a debug configuration and the _DEBUG macro is defined.
#define TRACE(x) std::cout << x << std::endl int main() { #ifdef _DEBUG TRACE("debug build"); #endif }
In C++17 this can be replaced with constexpr if as shown in the following example:
#include <string_view> inline void trace(std::string_view text) { std::cout << text << std::endl; } int main() { if constexpr(_DEBUG) trace("debug build"); }
Policy-like macros
OK, that’s not a term you see in the literature, but I couldn’t find something better, and that looks the closest to what we have here. Let’s look at the following example and exaplain what we’re doing.
The goal is to trace the execution of functions. We want a message to be displayed to the console when the function starts and another one when the function stops. The first message should show the function name and the current time, and the end message must show the function name, the current time and duration of the function execution. The class Tracer defines a conversion constructor, that prints a message to the console, and records a start time point, and a custom destructor, that computes the time since the constructor was called and prints another message to the console. Defining objects of this type at the beginning of a function will have the result that a message is printed after the function execution started and another one just before it ends. However, we only want to do that in some cases, when a particular macro name (called MONITORING in this example) is defined. This can be defined either in code, or passed as an argument to the compiler (like -DMONITORING). This goal can be achieved using macros, as in the following example:
#include <iostream> #include <string> #include <string_view> #include <chrono> #include "date.h" #include <ctime> #include <thread> #define MONITOR() Tracer tracer__LINE__(__FUNCTION__) class Tracer { public: Tracer(std::string_view function): function_name(function), start_time(std::chrono::system_clock::now()) { using namespace date; using namespace std::chrono; std::cout << "BEGIN [" << function_name << "] at " << start_time << std::endl; } ~Tracer() { using namespace date; using namespace std::chrono; auto end_time = std::chrono::system_clock::now(); auto diff = duration_cast<milliseconds>(end_time - start_time).count(); std::cout << "END [" << function_name << "] at " << end_time << " (duration " << diff << "ms)" << std::endl; } private: std::string function_name; std::chrono::system_clock::time_point start_time; }; #ifdef MONITORING #define MONITOR_FUNCTION() MONITOR() #else #define MONITOR_FUNCTION() #endif void foo() { MONITOR_FUNCTION(); std::cout << "executing..." << std::endl; using namespace std::chrono_literals; std::this_thread::sleep_for(1s); } int main() { foo(); }
If you run this program having MONITORING defined, the output looks like the following:
BEGIN [foo] at 2018-04-18 19:12:07.7385896 executing... END [foo] at 2018-04-18 19:12:08.7475495 (duration 1008ms)
Should MONITORING not be defined, the output is simply
executing...
Using constexpr if is not possible in this situation, because that would introduce an inner scope. In other words, the following example:
void foo() { if constexpr(MONITORING) Tracer tracer(__FUNCTION__); std::cout << "executing..." << std::endl; using namespace std::chrono_literals; std::this_thread::sleep_for(1s); }
would result in the following code being generated
void foo() { { Tracer tracer(__FUNCTION__); } std::cout << "executing..." << std::endl; using namespace std::chrono_literals; std::this_thread::sleep_for(1s); }
As a result, the Tracer object would be created and immediatelly destroyed at the beginning of the function.
A solution for this problem is to use policy-based design. We can define policies, i.e. classes, that perform or do not perform any tracing. The foo() function would become a function template, parameterized with the monitoring policy. Then, we can use std::conditional to select between policies at compile time based on a condition. That condition would be the availability of the MONITORING macro name. This can be passed as a compiler argument, or else it will be defined as 0 in the code. Here is how the example could look in this case:
#ifndef MONITORING #define MONITORING 0 #endif class Tracer { public: Tracer(std::string_view function): function_name(function), start_time(std::chrono::system_clock::now()) { using namespace date; using namespace std::chrono; std::cout << "BEGIN [" << function_name << "] at " << start_time << std::endl; } ~Tracer() { using namespace date; using namespace std::chrono; auto end_time = std::chrono::system_clock::now(); auto diff = duration_cast<milliseconds>(end_time - start_time).count(); std::cout << "END [" << function_name << "] at " << end_time << " (duration " << diff << "ms)" << std::endl; } private: std::string function_name; std::chrono::system_clock::time_point start_time; }; struct standard_monitor { standard_monitor(std::string_view function):t(function) {} private: Tracer t; }; struct no_monitor { no_monitor(std::string_view function) {} }; template <typename MonitorType> void foo() { MonitorType mt(__FUNCTION__); std::cout << "executing..." << std::endl; using namespace std::chrono_literals; std::this_thread::sleep_for(1s); } using monitor_type = std::conditional<MONITORING, standard_monitor, no_monitor>::type; int main() { foo<monitor_type>(); }
We are still left with two macros: MONITORING to select one policy or another, and __FUNCTION__ to get the undecorated name of the enclosing function. There is no way to replace the former for the time being, but for the latter, there is something under review in library fundamentals Technical Specification v2, called std::experimental::source_location. This will provide information about the source code, such as the line number and enclosing function name. Using this special built-in class, we would be able to get rid of the __FUNCTION__ special macro as following:
struct standard_monitor { standard_monitor(std::experimental::source_location loc = std::experimental::source_location::current()) :t(loc.function_name()) {} private: Tracer t; }; struct no_monitor { no_monitor() {} }; template <typename MonitorType> void foo() { MonitorType mt; std::cout << "executing..." << std::endl; using namespace std::chrono_literals; std::this_thread::sleep_for(1s); } using monitor_type = std::conditional<MONITORING, standard_monitor, no_monitor>::type; int main() { foo<monitor_type>(); }
Bonus: including files
Including files is definitely the most common preprocessor functionality. Is there an alternative to it? Not yet, but one is in work. It’s called modules and a technical specification is in work. It is likely that a first version will be available in C++20. Basically, the #include directive for headers, will be replaced with import directives for modules. Here is a very simple example:
#include <iostream> int main() { std::cout << "hello, world!" << std::endl; }
With modules available, this can be changed as follows:
import std.core; int main() { std::cout << "hello, world!" << std::endl; }
What about “stringifying”?
Ex: ENABLE_WINDOW(a) GetDlgItem(hDlg, IDC_TITLE##a)
@Pierre: that won’t be possible until we have static reflection in the language.
I just hate static_cast, and then extra layer of operators is unnecessary… I ended up with the following instead:
namespace permissions
{
enum : int
{
none = 0,
read = 1,
write = 2,
add = 4,
del = 8
};
}
void show_permissions(int const p)
{
if(p & permissions::read)
std::cout << "can read" << std::endl;
if (p & permissions::write)
std::cout << "can write" << std::endl;
if (p & permissions::add)
std::cout << "can add" << std::endl;
if (p & permissions::del)
std::cout << "can delete" << std::endl;
}
int main()
{
auto flags = permissions::read | permissions::write;
show_permissions(flags);
flags |= permissions::del | permissions::add;
flags &= permissions::write;
show_permissions(flags);
std::cout << permissions::write << std::endl;
}
Nice article. But the example of the TRACE macro in the “Conditional compilation” is not very strong. The typical definition of a TRACE macro would allow:
#ifdef NDEBUG
#define TRACE(x)
#else
#define TRACE(x) debug_printf(x);
#endif
void function_foo()
{
TRACE(“In function_foo.”);
}
the if constexpr construction requires 2 lines, which is more error prone.
Thanks for the comment. My intention was to show how we can replace conditional checks like #ifdef SOMETHING with constexpr if. Maybe the example I picked was not the most fortunate. Maybe it could have been something without another macro involved, several statements or something maybe harder to be simplified the way you did. But I believe I touched on the example you are mentioning in the other paragraph that I called policy-like macros.
#include
inline void trace(std::string_view text)
{
std::cout << text << std::endl;
}
int main()
{
if constexpr(_DEBUG)
trace("debug build");
}
This doesn't compile when _DEBUG is not defined (when -D_DEBUG is not passed to the compiler)
clang++ -std=c++17 conditional.cpp -o conditional -stdlib=libc++
conditional.cpp:11:16: error: use of undeclared identifier
'_DEBUG'
if constexpr(_DEBUG)
But, adding these lines allows compilation with and without -D_DEBUG and gives the desired effect.
#ifndef _DEBUG
#define _DEBUG 0
#endif
I am not sure if this the best way to fix it, but just that it works.
Another way to avoid conditional preprocessor based inline compilation, which can get messy, is to place platform or target specific code in separate but identically named source files in platform or target specific subdirs and use environment or build vars as input to the build system to determine which source files are compiled and linked. Along these lines, build specific code can be integrated into the class structure using virtual methods or subclassing.