HOME BLOG ARCHIVE TAGS

Tricks of the Trade #1: avoiding duplicities with C/C++ x-macros

October 08, 2017

Let’s consider the following simplified/contrived C++ variable usage issue 0:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// shared_state.h
extern const heavy_struct_t A1;
...
extern const heavy_struct_t AN;

// some_module.cpp
#include "shared_state.h"
// uses A1 ... AN;

// shared_state.cpp
extern const heavy_struct_t A1 = { ... };
...
extern const heavy_struct_t AN = { ... };

To avoid linking errors, shared instances must be defined. But two lists will be maintained: declarations at shared_state.h - for files like some_module.cpp - and definitions at shared_state.cpp.

This situation can be improved with x-macros 1. Encode entries for automatic preprocessor expansion, leveraging a macro definition that changes according to the context:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// shared_state.h
// "x" macro
#ifndef HEAVY_STRUCT_VAR
#define HEAVY_STRUCT_VAR(name) extern const heavy_struct_t name
#endif

#define HEAVY_STRUCTS()   \
    HEAVY_STRUCT_VAR(A1); \
    ...                   \
    HEAVY_STRUCT_VAR(AN)

HEAVY_STRUCTS();
#undef HEAVY_STRUCT_VAR

// some_module.cpp
#include "shared_state.h"
// can use A1 ... AN - HEAVY_STRUCTS() expanded DECLARATIONS

// shared_state.cpp
// redefine "x" macro
#define HEAVY_STRUCT_VAR(name) \
    extern const heavy_struct_t name = { ... }

#include "shared_state.h"
// also uses A1 ... AN - HEAVY_STRUCTS() expanded DEFINITIONS

Note that not all x-macro params must be used at all invocations. And the x-macro itself can sometimes be passed as another argument 2.


Notes:
[0] - Facebook’s HHVM is a real case study;
[1] - meta/generative mechanisms make software maintenance easier;
[2] - another good case for indirection;