TE
TechEcho
Home24h TopNewestBestAskShowJobs
GitHubTwitter
Home

TechEcho

A tech news platform built with Next.js, providing global tech news and discussions.

GitHubTwitter

Home

HomeNewestBestAskShowJobs

Resources

HackerNews APIOriginal HackerNewsNext.js

© 2025 TechEcho. All rights reserved.

My Favorite C++ Pattern: X Macros (2023)

131 pointsby ibobevabout 2 months ago

19 comments

jchwabout 2 months ago
X Macros are a classic C++ pattern. Less known than some of the more popular techniques like curiously recurring template pattern, but still quite common in large codebases.<p>(Although... it <i>is</i> a neat trick, but... It is kind of mostly useful <i>because</i> C++ macros are not very powerful. If they were more powerful, most uses of X Macros could be replaced by just having a single macro do all of the magic.)<p>I recently saw something I hadn&#x27;t seen before in a similar vein. There&#x27;s a million different bin2c generators, and most of them will generate a declaration for you. However, I just saw a setup where the bin2c conversion just generates the actual byte array text, e.g. &quot;0x00, 0x01, ...&quot; so that you could #include it into whatever declaration you want. Of course, this is basically a poor man&#x27;s #embed, but I found it intriguing nonetheless. (It&#x27;s nice that programming languages have been adding file embedding as a first-class feature. This really simplifies a lot of stuff and removes the need to rely on more complex solutions for problems that don&#x27;t really warrant them.)
评论 #43476798 未加载
评论 #43472825 未加载
评论 #43478045 未加载
kragenabout 2 months ago
This is really more a C pattern than a C++ pattern, isn&#x27;t it?<p>Frustrated by C&#x27;s limitations relative to Golang, last year I sketched out an approach to using X-macros to define VNC protocol message types as structs with automatically-generated serialization and deserialization code. So for example you would define the VNC KeyEvent message as follows:<p><pre><code> #define KeyEvent_fields(field, padding) \ field(u8, down_flag) \ padding(u8) \ padding(u8) \ field(u32, keysym) MESSAGE_TYPE(KeyEvent) </code></pre> And that would generate a KeyEvent typedef (to an anonymous struct type) and two functions named read_KeyEvent_big_endian and write_KeyEvent_big_endian. (It wouldn&#x27;t be difficult to add debug_print_KeyEvent.) Since KeyEvent is a typedef, you can use it as a field type in other, larger structs just like u8 and u32.<p>Note that here there are two Xes, and they are passed as parameters to the KeyEvent_fields macro rather than being globally defined and undefined over time. To me this feels cleaner than the traditional way.<p>The usage above is in <a href="http:&#x2F;&#x2F;canonical.org&#x2F;~kragen&#x2F;sw&#x2F;dev3&#x2F;binmsg_cpp.c" rel="nofollow">http:&#x2F;&#x2F;canonical.org&#x2F;~kragen&#x2F;sw&#x2F;dev3&#x2F;binmsg_cpp.c</a>, MESSAGE_TYPE and its ilk are in <a href="http:&#x2F;&#x2F;canonical.org&#x2F;~kragen&#x2F;sw&#x2F;dev3&#x2F;binmsg_cpp.h" rel="nofollow">http:&#x2F;&#x2F;canonical.org&#x2F;~kragen&#x2F;sw&#x2F;dev3&#x2F;binmsg_cpp.h</a>, and an alternative approach using Python instead of the C preprocessor to generate the required C is in <a href="http:&#x2F;&#x2F;canonical.org&#x2F;~kragen&#x2F;sw&#x2F;dev3&#x2F;binmsg.py" rel="nofollow">http:&#x2F;&#x2F;canonical.org&#x2F;~kragen&#x2F;sw&#x2F;dev3&#x2F;binmsg.py</a>.
评论 #43480352 未加载
评论 #43482234 未加载
loegabout 2 months ago
I hate X-Macros, but they&#x27;re very useful in some situations. We use them to generate an enum of error values and also a string table of those names. (Unlike two separate tables, the X macro version is guaranteed to be the exact correct length &#x2F; values align with the corresponding string.)
评论 #43476166 未加载
评论 #43475692 未加载
Blackthornabout 2 months ago
I understand the use case of this, but when I see it I always wonder if, and think I would prefer, some external code generation step instead rather than falling back on macros in the preprocessor. Like an external script or something.
评论 #43472456 未加载
评论 #43473244 未加载
评论 #43478405 未加载
评论 #43472340 未加载
评论 #43472733 未加载
评论 #43481141 未加载
评论 #43473664 未加载
评论 #43472333 未加载
评论 #43475422 未加载
评论 #43479762 未加载
评论 #43473352 未加载
kevin_thibedeauabout 2 months ago
&quot;X&quot; macros are great until you need two of them visible in the same translation unit. It is much better to pass a list macro as an argument to a uniquely named X macro and avoid the need to ever undef anything.
评论 #43473439 未加载
wat10000about 2 months ago
Some minor tweaks to what the author shows to make it even better.<p>Give the macro a more descriptive name. For their example, call it GLOBAL_STRING instead of X. I think this helps make things clearer.<p>#undef the macro at the end of the header. That removes one line of boilerplate from every use of the header.<p>Use #ifndef at the top of the header and emit a nice error if the macro isn&#x27;t defined. This will make it easier to understand what&#x27;s wrong if you forget the #define or misspell the macro.
评论 #43473150 未加载
malkiaabout 2 months ago
I&#x27;ve learned about them, staring endlessly at the luajit source code - for example (not the best example I can remember, but still) - <a href="https:&#x2F;&#x2F;github.com&#x2F;LuaJIT&#x2F;LuaJIT&#x2F;blob&#x2F;v2.1&#x2F;src&#x2F;lj_lex.h#L15" rel="nofollow">https:&#x2F;&#x2F;github.com&#x2F;LuaJIT&#x2F;LuaJIT&#x2F;blob&#x2F;v2.1&#x2F;src&#x2F;lj_lex.h#L15</a><p>there it defines a TOKEN(_,__) macro generator the luajit token&#x2F;keywords and later generates enums with them.<p>I&#x27;ve used it recently to wrap a &quot;C&quot; api with lots of functions, such that I can redirect calls.
KerrAvonabout 2 months ago
I can&#x27;t believe this actually has a name (is this an attempt to make fetch happen?) and it&#x27;s not considered an anti-pattern. This sort of preprocessor&#x2F;code mixing is impossible to debug and maintain; most senior C++ programmers have advised people to avoid doing this since the 1990&#x27;s.<p>There is a role for the preprocessor to automate simple tables in C and such, but anything complex should be avoided like the plague if you don&#x27;t like tech debt. And there&#x27;s not much excuse for using it in C++ to this extent.
评论 #43475842 未加载
评论 #43474210 未加载
gibibitabout 2 months ago
It is a clever trick. Very useful in C also, maybe more than in C++.<p>It can be overused, though.<p>Kind of works like Rust declarative macros (`macro_rules!`) in that it is often used to repeat and expand something common across an axis of things that vary.<p>It&#x27;s funny that the simple name X Macros has stuck and is a de facto standard. E.g. <a href="https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;X_macro" rel="nofollow">https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;X_macro</a> suggests they date to the 1960s.
PaulHouleabout 2 months ago
Famous in IBM 360 assembly language, in particular these were used for building code based on data schemas in CICS [1] applications which were remarkably similar to 2000-era cgi-bin applications in that you drew forms on 3270 terminals [2] and instead of sending a character for each keystroke like the typical minicomputer terminal, users would hit a button to submit the form, like an HTML form, and the application mostly shuttled data between forms and the database.<p>[1] <a href="https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;CICS" rel="nofollow">https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;CICS</a><p>[2] <a href="https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;IBM_3270" rel="nofollow">https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;IBM_3270</a>
senozhatskyabout 2 months ago
That&#x27;s sort of how Linux kernel trace-events are implemented [1]. E.g.<p><pre><code> DECLARE_EVENT_CLASS(sched_wakeup_template, TP_PROTO(struct task_struct *p), TP_ARGS(__perf_task(p)), TP_STRUCT__entry( __array( char, comm, TASK_COMM_LEN ) __field( pid_t, pid ) __field( int, prio ) __field( int, target_cpu ) ), TP_fast_assign( memcpy(__entry-&gt;comm, p-&gt;comm, TASK_COMM_LEN); __entry-&gt;pid = p-&gt;pid; __entry-&gt;prio = p-&gt;prio; &#x2F;* XXX SCHED_DEADLINE *&#x2F; __entry-&gt;target_cpu = task_cpu(p); ), TP_printk(&quot;comm=%s pid=%d prio=%d target_cpu=%03d&quot;, __entry-&gt;comm, __entry-&gt;pid, __entry-&gt;prio, __entry-&gt;target_cpu) ); </code></pre> [1] <a href="https:&#x2F;&#x2F;web.git.kernel.org&#x2F;pub&#x2F;scm&#x2F;linux&#x2F;kernel&#x2F;git&#x2F;torvalds&#x2F;linux.git&#x2F;tree&#x2F;include&#x2F;trace&#x2F;events&#x2F;sched.h?h=v6.14" rel="nofollow">https:&#x2F;&#x2F;web.git.kernel.org&#x2F;pub&#x2F;scm&#x2F;linux&#x2F;kernel&#x2F;git&#x2F;torvalds...</a>
评论 #43478877 未加载
sfpotterabout 2 months ago
No need for any of this if you use D!
a_t48about 2 months ago
X Macros are great, but always happy when I can replace them with something else.
randomNumber7about 2 months ago
One the one hand it is great and probably usable to solve actual problems.<p>On the other hand it seems fishy that you need all these hacks to do it. There must be a way simpler language than C++ where you could do the same easier.
saagarjhaabout 2 months ago
Generally I find that most uses of x macros are obviated by an increasingly powerful constexpr.
lowbloodsugarabout 2 months ago
We called these “fruit lists”.
pjmlpabout 2 months ago
This is a C pattern, that people also happen to use in C++ and Objective-C.
jdnxndndabout 2 months ago
How is this C++? This is cpp (c preprocessor)
评论 #43475803 未加载
webdevverabout 2 months ago
i hate this &quot;trick&quot; with a passion. i am the one who winds up having to debug this shit, and theres nothing i love doing more than trawling through hundreds of thousands of preprocessed C++ that gets defecated by `gcc -E ...`. even better when the macro expansion is 10 levels deep.<p>you need tables? use python (or your scripting language of choice) as a pre-compile step that generates the .cpp&#x2F;.c&#x2F;.h files, and then build <i>that</i>.
评论 #43480607 未加载