A type-generic statement macro expands to statements rather than an expression: it performs an operation without producing a value. Unlike an expression macro that relies on arithmetic conversions, it can use types supplied as macro arguments to declare suitable local variables. section 18.1.3
#define dataCondStore(T, BASE, P, E, D) \
do { \
T* _pr_p = (P); \
BASE _pr_expected = (E); \
BASE _pr_desired = (D); \
bool _pr_c; \
do { \
mtx_lock(&_pr_p->mtx); \
_pr_c = (_pr_p->data == _pr_expected); \
if (_pr_c) _pr_p->data = _pr_desired; \
mtx_unlock(&_pr_p->mtx); \
} while(!_pr_c); \
} while (false)
The dataCondStore listing is a statement-form example. Its T and BASE parameters supply the pointer and value types; P, E, and D are captured in local variables before the locking loop. section 18.1.3
Two details make the expansion usable at a call site:
- The outer do ... while(false) lets the invocation behave syntactically like a void function call, including its trailing semicolon. section 18.1.3
- Local variables evaluate each macro argument at most once. The inner loop then locks the mutex, compares data with the expected value, conditionally assigns the desired value, and unlocks. section 18.1.3
TAKEAWAY: Grouping statements in a macro can provide a type-adaptable, void-like interface, but proper coding involves a tradeoff in ugliness and maintenance. Do not confuse this pattern with <tgmath.h> macros or _Generic-based dispatch, which select type-appropriate functions for expression calls. section 18.1.3 section 18.2

