[HN Gopher] The Preprocessor Iceberg Meme ___________________________________________________________________ The Preprocessor Iceberg Meme Author : camel-cdr Score : 199 points Date : 2022-07-14 11:53 UTC (11 hours ago) (HTM) web link (jadlevesque.github.io) (TXT) w3m dump (jadlevesque.github.io) | tasty_freeze wrote: | This one has bit me twice in the past 35 years. | | I have some program I'm working on, doing the usual | edit/compile/run/debug cycle. At some point I decide to compare | two versions of some section of code, so I write out temporary | files of the old section named "old" and the new section named | "new". Then compiles start failing, but oddly it is a file that I | haven't edited recently. | | The issue is that some code (not necessarily even mine) has an | "#include <new>" and it is picking up my temporary file named | "new". | gpderetta wrote: | One of the most odd issues I have encountered was a test case | that would fail if one random log line was deleted (which would | normally means UB or timing issues) but, wildly, not when the | log line was commented out. Turns out it was interaction | between the use of __LINE__ in a macro to generate unique | identifiers and a violation of the One Definition Rule. | hermitdev wrote: | Heh. If I had a nickel for every time I shot myself in the foot | over the years by dropping a temporary file named "test.py" | somewhere... I'd don't know about rich, but I'd probably at | least be able to buy myself a coffee. | camel-cdr wrote: | Since some people missed this, every entry is a clickable link. | dosshell wrote: | explanations: https://jadlevesque.github.io/PPMP- | Iceberg/explanations | rwmj wrote: | Recently started hiding "... do" and "while (0);..." in macros to | write nicely bracketed start and end C macros eg this set for | generating HTML: | | https://github.com/libguestfs/libguestfs-common/blob/master/... | | You can write: start_element ("memory") { | attribute ("unit", "MiB"); string_format ("%d", | g->memsize); } end_element (); | | to generate <memory unit="MiB">1024</memory> | ttoinou wrote: | Why do you need to start with while (0); ? | calahad wrote: | https://bruceblinn.com/linuxinfo/DoWhile.html | billforsternz wrote: | Looks good. I wonder why this trick is invariably implemented | with "do {" and "} while (0)" and never with "if(1) {" and "} | else" ? | taspeotis wrote: | I think in part because do ... while expects a ; at the end | so you are obliged to provide one, which makes the macro feel | more like a "real" function call. | | if ... else {} you could omit the ; | billforsternz wrote: | Good point, thank you. The while (0) demands the expected ; | The trailing else hopes for the expected ; but would | tolerate a wide range of nonsense instead. | webstrand wrote: | That's pretty cool. It's been a while since I've done C, but | couldn't you use a `for` loop instead of a while and perform | any necessary cleanup in the "update" section? i.e. | https://gcc.godbolt.org/z/jq84jondh | | (The condition is optimized away by the big three: msvc, clang, | and gcc) | stelonix wrote: | It's a huge reminder C++ is missing a proper, hygienic, macro | system. Too many things that are a pain to do in C++ would be | easy with a real macro language. I have hope we'll get it | sometime this decade, seeing all the work on the language since | C++11 still happening more than 10 years later. | | It's worth nothing that a macro system plus basic reflection is | where the real power of macros lies at. | otabdeveloper4 wrote: | > It's a huge reminder C++ is missing a proper, hygienic, macro | system. | | Templates, man. | gumby wrote: | In case someone reads this comment as sarcasm: I got into a | convo with Stroustrup about this once, back in the 90s. I | said one thing I missed was the lack of macros, and he made a | glancing comment about the preprocessor which I obviously | dismissed and said didn't even count. He bitterly said, | "Yeah, unfortunately when something like that pollutes an | ecological niche it becomes impossible to eradicate. The best | I could get away with was templates." | | A good perspective. | WalterBright wrote: | Bjarne was right, although C++ has been slow to adopt | replacements for the preprocessor (such as modules, and | conditional compilation). | WalterBright wrote: | Don't forget function templates! D can do them, too, but we | _strongly_ discourage their use. The trouble is that people | use them to create DSLs that are indistinguishable from C++ | code. For example: #include | <boost/spirit.hpp> using namespace boost; int | main() { spirit::rule<> group, fact, term, expr; | group = '(' >> expr >> ')'; fact = | spirit::int_p | group; term = fact >> *(('*' | >> fact) | ('/' >> fact)); expr = term >> *(('+' | >> term) | ('-' >> term)); assert( | spirit::parse("2*(3+4)", expr).full ); assert( ! | spirit::parse("2*(3+4", expr).full ); } | | https://studylib.net/doc/10029968/text-processing-with- | boost... slide 40 | | What's C++ and what's regex? | xigoi wrote: | Aren't these just normal parser combinators? | smitty1e wrote: | I'm standing by for the announcement that some caffeine- | addled Boost metaprogramming madman has implemented the Rust | borrow checker as a C++ template, or at least thinks that he | may have, when the compilation completes sometime in the | 2030s. | steveklabnik wrote: | https://docs.google.com/document/d/e/2PACX-1vSt2VB1zQAJ6JDM | a... | smitty1e wrote: | It's not clear how much boost.org magic they used. | Failing that, a GCC extension could be needful. | | From the ref: | | --- | | Conclusion | | We attempted to represent ownership and borrowing through | the C++ type system, however the language does not lend | itself to this. Thus memory safety in C++ would need to | be achieved through runtime checks. | | Example code | | #include <type_traits> | | #include <utility> | | #include <assert.h> | | #include <stddef.h> | sudosysgen wrote: | Why use template metaprogramming? You can use compiler | extensions for that since the code will be valid either way | smitty1e wrote: | C++ has the Maximum Pain Rule. | | Templates it must be. | wyldfire wrote: | There is a branch of clang with support for "-Wlifetime" | which might be a simpler alternative. | gpderetta wrote: | It is not like I didn't try... | stelonix wrote: | Templates are nice, but they have shortcomings a more generic | macro system wouldn't. They also have the issue where the | more complex is your task, the more convoluted the code has | to look, compilation times also increase and parsers (ergo, | IDEs too) have trouble giving meaningful info on parameters. | Don't even get me started on template errors because that's | an atrocity on another level :( | chakkepolja wrote: | Can it be used to implement automatic serialization on simple | struct / class types? | aaaaaaaaaaab wrote: | If you only use std::tuple, then yes xD | gpderetta wrote: | There is a nasty trick which uses structured binding to | convert an arbitrary aggregate to a tuple. | | That still doesn't help if you want the fields names. | otabdeveloper4 wrote: | No, because C++ templates are basically a Lisp, and only | things that are list-like can be processed by templates. | (Like tuples, for example.) | gpderetta wrote: | But there are automated ways to to convert aggregates to | tuples. | pjmlp wrote: | Other than some more clever hacks, templates alongside | constexpr already cover a lot of possibilities. | sumtechguy wrote: | Most of the abuses of #define I have seen, a template or | constexpr takes care of. There are still some cases where it | is nice. But many times you probably should just write a | function/method/template out of it anyway. | Asooka wrote: | You don't get the guarantees for what code is generated that | you do with macros and they take a lot longer to compile. | Also you can't just modify the AST of the current scope like | you can with macros - pretty much every single time I have to | use a macro it's because I need to generate code within the | current scope. Fortunately they are usually very short. | | The two for me fill different niches - for generic type-safe | functions, parametrised types, etc. - templates. For text | generation - macros. A hygenic macro system that lets you | generate AST nodes and gives you access to type information | would be absolutely divine, but it doesn't seem like we're | getting it. Imagine if we had a script language that had full | access to the compiler's internals. | pjmlp wrote: | I don't need to imagine when we have Circle, and | experimental implementations of C++ reflection. | | That is what is missing piece of the puzzle. | kazinator wrote: | Actually, some of the problem may be with C++ itself. When the | C preprocessor is used in a more flexible, dynamic language, | you can do surprising things. | | In the cppawk project, which combines the preprocessor with | Awk, I used the preprocessor to create an iteration syntax with | a vocabulary of useful clauses that combine together for | parallel or cross-product iteration. | | Furthermore, the clauses are user-definable. | | https://www.kylheku.com/cgit/cppawk/about/ | | What helps is that you don't have to deal with types and | declaration syntax. So many of the ideas in cppawk will not | translate back to C or C++, or not without wrecking the syntax | with additional arguments and whatnot. | | The man page for the <iter.h> header has a section on defining | a clause; I provided an example of defining a clause that | iterates on alpha-numeric string ranges like from "A00" to | "Z99". | | As an experienced Lisp programmer (and implementor), I had to | rub my eyes several times to believe I had such a thing | working, under such a universally maligned and reviled | preprocessor. | sk0g wrote: | Unrelated, but yesterday I found out you can have two bools in | C++ that are both true but do not equal each other, by | reinterpret casting them from (u)ints. I think this was for the | standard bool type too... Now I'm questioning the most basic of | things. | pjmlp wrote: | It boils down to C compatiblity, every non zero numeric value | is true, that language where good developers make no errors. | sk0g wrote: | I know that's the case for ints, but in this case [0] the int | was cast to a boolean, which I thought would ensure | comparisons would perform as expected, but no such luck. | | [0] https://www.onlinegdb.com/tfHPZ_FOfY | gpderetta wrote: | You are not casting an int to a bool (which would indeed do | the right thing) but casting a pointer to int to a pointer | to bool which violates strict aliasing. | sk0g wrote: | Ah fair, thanks for pointing it out! Wasn't my issue, | just the minimal reproduction of behaviour that happened | over a few external modules, causing this issue. | bobbyi wrote: | The strict aliasing violation is incidental. You can | change int8_t to char in the code and get the same result | with no strict aliasing violation. | gpderetta wrote: | Sure, as per my other comment, it then would be an object | representation violation. | | Even ignoring both rules, there is still no reason to | expect the assertion not to fire: int x | = 2; assert(*(bool*)x == true); | | This is the same as: float x = 2.0 | assert(*(int*)x == 2); | | Just because two object are convertible, there is no | reason to expect their representation to be the same. | jstimpfle wrote: | This is not strict aliasing as far as I understand. | Strict aliasing is about inference of distinctness of | pointers. The case here is that an invalidly typed | pointer is created (a bool pointer pointing to where | there is no bool). Not sure what this situation is called | in standardese. | gpderetta wrote: | The type aliasing rule is also known as the strict | aliasing rule, see for example: | https://en.cppreference.com/w/c/language/object | jstimpfle wrote: | After skimming this, I still think the strict aliasing | rule is used by compiler to avoid re-reads. What you were | talking about is probably something else, maybe an | "invalid lvalue access" as per your link. | gpderetta wrote: | From cpp reference: "Strict Aliasing: Given an object | with effective type T1, using an lvalue expression | (typically, dereferencing a pointer) of a different type | T2 is undefined behavior, unless [non relevant exceptions | omitted]". | | In this case the expression has type bool and the | underlying object has type int, so it is a | straightforward strict aliasing violation. | | With GCC you can compile with -fno-strict-aliasing to | ignore this rule. But now you fall afoul of the rule that | prevents accessing an invalid representation (i.e. a | trap-representation) of an object. This rule is also | described in the link I posted before, under the object | representation paragraph. | jstimpfle wrote: | ok, so in the case above, it's both (if strict aliasing | is active). Makes sense now. | jstimpfle wrote: | Sometimes people indeed come up with stuff where I can't | figure out why anyone would ever write that. | sk0g wrote: | It wasn't really one particular function, but an | aggregation of behaviours over some external modules. None | of it written by me, luckily :) | MauranKilom wrote: | I'm very sure that's just UB. The standard requires unique | representations of true and false (e.g. byte values of 1 and 0, | but for all you care it could be 13 and 37). Converting an | integer to bool (even implicitly, as in `if (3)`) is required | to lead to those values in the abstract machine. | | If you somehow force a bool to be a different value (e.g. | `*(int*)(&myBool) = 7`), that's UB. | sk0g wrote: | It's operating on a boolean still though, here's an example - | https://www.onlinegdb.com/tfHPZ_FOfY | gpderetta wrote: | Line 21 and 22 are UB, so the program has no meaning | according to the standard. | elteto wrote: | This is undefined behavior. Compile with -fsanitize=undefined | and run it, you will get a runtime error. | sk0g wrote: | And so it does! Thank you, will see if I can keep it to | prevent me from running into this issue. I have a feeling the | Unreal codebase will be full of UB abuse though. | elteto wrote: | Unfortunately most large C++ codebases are... | Asooka wrote: | Clang, MSVC and GCC all have options to turn off various | flavours of UB, or rather to define the behaviour in | those cases. I strongly suggest using -fwrapv -fno- | strict-aliasing -fno-delete-null-pointer-checks (and the | equivalent in MSVC) in every large project. That is the | easiest UB to hit and while the program will have a bug, | it will at least be easier to reason about and optimised | vs debug builds will have the same behaviour. Debugging | "the compiler deleted my if meant to catch and log an | error condition because after the inlining pass some | function dereferences a pointer, thus the pointer cannot | be null, thus the if can be deleted" is... hard. | pjmlp wrote: | Additionally all of them have the ability to enable | bounds checking and iterator validation in the standard | library. | MauranKilom wrote: | Can someone explain what #if | static_cast<bool>(-1) | | is about? | | My thoughts were "that's just #if true, no?", then "wait, | static_cast is not part of the preprocessor, that can't work" to | "wtf, it actually compiles"... | | Edit: As people point out, you can click on it to get context. | And yeah, that one is an oof. | adzm wrote: | > The #if statement replaces, after macro expansion, every | remaining identifier with the pp-number 0. So #if | static_cast<bool>(-1) is equivalent to #if 0<0>(-1), #if 0 > | -1, and #if 1. | | Wow, I have no idea why this would ever be done. | sudosysgen wrote: | Because the preprocessor doesn't know what static_cast is, so | in an if it just evaluates to false as do any values that | haven't been defined. | jcelerier wrote: | well it's the consequence of #if SOME_STUFF | | evaluating to 0 if SOME_STUFF isn't defined | kazinator wrote: | > _I have no idea why this would ever be done._ | | You'd never literally write #if | static_cast<bool>(-1) | | but it could be the result of a macro expansion. | kazinator wrote: | I suspect the point is that the preprocessor language | expression syntax that is out of whack with the host languages | it is integrated into. If you hoist an expression of the | language proper into a preprocessing directive, you may get | gibberish. | | This could happen by accident, particularly through layers of | macros: | | Say you have: #if SOME_MACRO(ARG) | | originally, this expands to an constant expression in which | everything is an integer; then someone edits the macro. Things | may still compile, but the expression is gibberish, not doing | what it looks like it's doing. | | The macro could be used in non-preprocessing contexts: | int x = SOME_MACRO(X); if (SOME_MACRO(Y)) ... | | so that programmer might have a good reason for editing it; | just they didn't notice it's also used in an #if directive. | Dav3xor wrote: | This illustration is missing a layer. Buried down in the silt, | under the abyssal plane at the bottom of the ocean.. Ken | Thompson. | gpderetta wrote: | This is awesome! | | The C/C++ preprocessor is probably the esoteric programming | language that see the most real world use. Or would that be C++ | template metaprogramming...? | WalterBright wrote: | Macro systems inevitably wind up being used to create a | specialized undocumented language that nobody but its creator | understands. | | I know how enticing they are, I designed and implemented one | myself for the ABEL programming language. I used lots of clever C | macros in my C programming, and was proud of them. | | But, eventually, I removed all the macro usage, and quite | preferred the resulting code. It was cleaner and easier to read. | | It's not just C macros. It's the same for assembler macros. I've | heard from others it's the same for other languages that rely on | macros. | | Essentially, macros are a cheap way to add power to a language. A | better way is to add proper metaprogramming features. This is the | route we chose to go with D, and it is satisfyingly successful. | | Macros - just say no. | stingraycharles wrote: | I can hear the sound of a thousand LISP devs hurting in parens | reading this comment lol. | | Macros, as with most things (including even goto!) have their | place, the problem is when they're abused. But to say they're | never useful ever and you should instead always rely on | language features is not something I agree with, and could even | lead to language bloat if you need a full fledged feature for | every little thing which would be trivially solved with a | macro. | WalterBright wrote: | My unfettered opinion is that Lisp has not really caught on | because it _relies_ on macros to make it useful. Every | project invents their own language on top of Lisp, | incompatible with anyone else 's. | | It's like the problem with C++ before C++98. It had no string | class, so everybody invented their own, all incompatible with | everyone else's. | | BTW, everyone says that they understand my point and use | macros modestly and responsibly. Nearly all of them go on to | create their own undocumented impenetrable language out of | those macros. | | It takes a programmer about 10 years of creating and using | macros and dealing with other peoples' macros to come to the | conclusion that the whole feature needs to be scrapped. | Sadly, there aren't any shortcuts to this realization :-) | WalterBright wrote: | It also took the C++ community about 10 years to realize | that the way iostreams was doing operator overloading to do | pipelining was an abomination as well. | | In the D community, we also strongly discourage operator | overloading for any purpose other than creating arithmetic | types. | pjmlp wrote: | Not everyone shares that point of view. | | Gladly using iostreams since 1993. | tialaramex wrote: | I am doubtful that even WG21 as originally constituted | would have accepted I/O Streams with its "Look at me, | I've got operator overloading" operator abuse if it | wasn't Stroustrup's own code. If some outsider had come | along and said "Look at this slower, clumsier, operator | abusing alternative to C's stdio" the committee might | have quoted Stroustrups' own words condemning such abuse. | "the ability to define new meanings for old operators can | be used to write programs that are well nigh | incomprehensible". | | I'm with you up to a point on overloading, if it were up | to me for example Rust would not implement Add and | AddAssign on String, and certainly Java wouldn't special | case += but we are where we are. | | However Rust has several operators (fewer than C++ but | still several) that aren't just for arithmetic types. | Deref and DerefMut of course (used to implement smart | pointers such as Arc), Index and IndexMut (for the | indexing operator []) but also Try (implementation of the | ? operator) and (though rather more distant into your | future than Try if you write Stable Rust) the Function | operator traits Fn, FnMut and FnOnce which represent | callables. | | Of course arguably Rust isn't _overloading_ operators at | all. Rust has no subtyping, and so whether you can Add or | Multiply or Try something is a matter only of whether | that type implements the associated Trait. | MrBuddyCasino wrote: | > My unfettered opinion is that Lisp has not really caught | on because it relies on macros to make it useful. Every | project invents their own language on top of Lisp, | incompatible with anyone else's. | | Been saying this for years - this way of programming is | powerful for the lone hacker, but lethal for team efforts. | I will never forget the guy who ported some weird function | evaluation framework from Clojure to a Java app and then | left for greener pastures, what he left behind was the | gnarliest of mindfucks. | vitiral wrote: | what if instead of the macros being a side language, they _are_ | the language? (github.com/civboot/fngi) | arka2147483647 wrote: | I have seen this kind of "flip-flop" behaviour people have with | macros a few times. First you go all in, burn yourself, and | then go to the other extreme. | | Personally, i think macros are a good way to automate some | common tasks, but you have to be carefull to keep them short. | Also it is a good idea to prune macros periodically to remove | what you dont need. | | In Cpp, If you find yourself choosing weather to use a macro or | a template; Choose the one which is more terse! | | Also macros will always inline in debug while templates will | generate functions in debug builds, without optimizations. This | may be an important performance consideration at times. | WalterBright wrote: | D has an "always inline" annotation for functions, so they'll | be inlined even in debug builds if so desired. | | Using a symbolic debugger with macros is just another facet | of the slow moving disaster of macro usage. | spaetzleesser wrote: | What are these meta programming features if I may ask? | tialaramex wrote: | I think Rust's hygienic and declarative "by example" macros are | very nice actually. You could of course do the same things with | its procedural macros but that's messy and harder to maintain. | Appropriate tools for the job, don't use a chainsaw to trim | your rosebush. | userbinator wrote: | Relatedly, this article is claimed to be a description of the | algorithm that the C standard intended; it is surprisingly | succinct: https://news.ycombinator.com/item?id=22444447 | sebastialonso wrote: | Love this iceberg with explas for PLs. Does anyone know another | one? | fouronnes3 wrote: | I made a general one about C++ some time ago: | https://fouronnes.github.io/cppiceberg/ | hwers wrote: | Anyone know what SIMD means at the bottom layer is here? I know | what SIMD is I mean in the context of the preprocessor (and being | the worst offender apparently). | gpderetta wrote: | It is not obvious, but the entries are clickable! It [0] is | apparently a technique for speeding up processing of | preprocessor lists. | | edit: it is basically loop unrolling. | | [0] https://jadlevesque.github.io/PPMP- | Iceberg/explanations#sing... | camel-cdr wrote: | Sorry, I misread your comment as clickbait instead of | clickable. | | ~~I can see how others are somewhat clickbait, but this is | literally single instruction/continuation multiple data. It | uses a single step in the continuation mechanism to compute | on multiple elements of data to speed up the computation.~~ | gpderetta wrote: | No worries, I probably misread your comment too :). I | thought you were asking what SIMD/SCMD is in this context, | but as the submitter you probably already did, and already | knew that the links were clickable. | | BTW: I don't think the titles of each entry are | particularly clickbait-y. | pavlov wrote: | You can click on each item for an explanation. | kazinator wrote: | I wrote a useful extension to the C preprocessor for GCC, and | submitted it to the gcc-patches mailing list in April. This went | unnoticed, as have my subsequent pings since. I'm planning to | ping once a month from now on until the rest of 2022, and then | switch to quarterly. | | https://gcc.gnu.org/pipermail/gcc-patches/2022-April/593473.... | | __EXP_COUNTER__ gives a macro expansion to a numeric value which | uniquely enumerates that expansion. | | The sister macro __UEXP_COUNTER__ allows a macro expansion to | access the parent's value: if a macro is being expanded in the | body of another macro, one level up, it provides the | __EXP_COUNTER__ value of that parent macro. | | This feature solves the problem of producing unique names in a | macro. (Unique within a translation unit.) | | The __LINE__ symbol gets abused for this. The problem is that | it's not unique. A macro can be called two or more times in the | same line of code. Moreover, the same line number like 42 can | occur multiple times in the same translation unit due to | #include; a line number is not unique within a translation unit. | | __COUNTER__ is next to useless because on each access, its value | changes. It's useful in a situation in which a name is needed | syntactically, and has to be unique, but is otherwise never | referenced: just mentioned once and that's it. | | Multiple references to __EXP_COUNTER__ in the same macro | expansion context produce the same value. | aaaaaaaaaaab wrote: | >I'm planning to ping once a month from now on until the rest | of 2022, and then switch to quarterly. | | I would do the opposite: keep halving the ping interval until | they notice. | gpderetta wrote: | I'm probably missing something, but can't you use counter to | generate a unique name once and then forward to to another | macro so that it can be used multiple times? | kazinator wrote: | Possibly. | | Whereas definitions are lazily evaluated, so that if we have: | #define COUNT __COUNTER__ | | each occurrence of _COUNT_ behaves like an invocation of | ___COUNTER___. | | But, this is not true of parameters because they get expanded | before substitution. $ gcc -E - #define | MAC(PARAM) PARAM PARAM PARAM MAC(__COUNTER__) | # 1 "<stdin>" # 1 "<built-in>" # 1 "<command- | line>" # 31 "<command-line>" # 1 | "/usr/include/stdc-predef.h" 1 3 4 # 32 "<command- | line>" 2 # 1 "<stdin>" 0 0 0 | | Thus perhaps you may be able to get something like | ___EXP_COUNTER___ by splitting your macros into interface and | implementation: #define MAC_IMPL(A, B, | EXP_COUNTER) #define MAC(A, B) MAC_IMPL(A, B, | __COUNTER__) | | I'm guessing this is what you mean by forwarding. | | This could be a pretty major inconvenience, if you have to do | it in the middle of a situation that is already stuffed with | preprocessing contortions. Like say you had to define 32 | macros that are similar to each other, for whatever reason, | and you want this hack: now you have 64. | gpderetta wrote: | Yes the interface/impl split is what I had in mind. | | Even only moderately complex PP metaprogramming requires | multiple rounds of wrapping, so I'm not sure it is such a | big burden. | kazinator wrote: | By the way, I'm also interested in solving the "no | recursive macro" problem hinted at in this submission. | While working on __EXP_COUNTER__, I looked into it a bit. | | The big issue is that the GNU C preprocessor uses global | state for tracking expansion. In effect, it takes | advantage of the no-recursion rule and says that during a | macro's expansion, only one context for that expansion | needs to exist. That context is patched into the macro | definition, or something like that. (I don't have the | code in front of me and it's been a few months.) The | preprocessor knows that there is a current macro being | expanded, and there is a stack of those; but that is | referenced by its static definition, which has a 1:1 | relationship to expansion state, like parameters, | location and whatnot. That might have to turn into a | stack, perhaps; there is a refactoring job there, and the | code is a bit of a hornet's nest. | | In terms of syntax/deployment, it would be easy. I | envision that there could be a #defrec directive that is | like #define, but which creates a macro that is blessed | for recursive expansion. Or other possibilities: #pragma | rec(names, of, macros, ...) which is better for code that | has to work without the extension, since it uses #define. | rwmj wrote: | We use this (trick taken from stackoverflow) to make | __COUNTER__ usefully provide unique, reusable names: | | https://gitlab.com/nbdkit/nbdkit/-/blob/master/common/includ... | | Example use for MIN and MAX macros which evaluate the | parameters once and can be nested to any depth: | | https://gitlab.com/nbdkit/nbdkit/-/blob/master/common/includ... | | I don't know what __EXP_COUNTER__ would add. | kazinator wrote: | Your NBDKIT_UNIQUE_NAME(name) cannot produce the same name | twice because it doesn't take a counter as a parameter. | | __EXP_COUNTER__ adds the ability for a macro expansion to | have its own counter for that expansion instance, without | some other macro having to hand it one as a an extra, visible | parameter. | jstimpfle wrote: | As someone who understands C macros relatively well and who | makes frequent use of them, I don't think I understand what | __EXP_COUNTER__ does, and how it is different from __COUNTER__. | I would have to experiment each time before using it, and would | then quickly forget again the intricate details about expansion | order, etc., similar to how every time I do some kind of | STRINGIFY macro I have to make sure to use the right number of | forwarding macro calls. | | Is there a concrete use case for this that really can't be | solved by __LINE__? I've used __LINE__ in the past to generate | unique identifiers used in macro-generated code chunks. I don't | see that non-uniqueness thing you mentioned causing any | problems except for global variables (so not really an issue in | my book). | | As much as I love the C preprocessor as a crude tool that can | solve many practical issues that are solved in other languages | with a magnitude more complexity (besides solving problems that | other languages don't have), I think the value doesn't come | from its unintelligible execution model. And if __EXP_COUNTER__ | is so difficult to understand, I personally don't like it. | kazinator wrote: | __EXP_COUNTER__ has a stable value in a given token | replacement sequence (right hand side of a macro). | | __COUNTER__ __COUNTER__ __COUNTER__ might give you 42 43 44, | whereas __EXP_COUNTER__ __EXP_COUNTER__ __EXP_COUNTER__ will | produce 73 73 73. | | We can imagine that every macro has a hidden parameter: | #define MAC(A, B, C, __EXP_COUNTER__) | | we don't pass this parameter when calling the macro; the | macro expander does that, and it passes an integer token | whose value is incremented for each such call. | jstimpfle wrote: | Thanks, it's a little clearer in my mind now. But wouldn't | this work for you? #include <stdio.h> | #define FOO_(c) printf("%d %d %d\n", c, c, c); | #define FOO() FOO_(__COUNTER__) #define FOO2() | FOO_(__COUNTER__) int main(void) { | printf("%d %d %d\n", __COUNTER__, __COUNTER__, | __COUNTER__); FOO(); FOO(); | FOO2(); FOO2(); return 0; | } | | Output: 0 1 2 3 3 3 4 4 4 | 5 5 5 6 6 6 | scratcheee wrote: | The parent comment explained pretty well the advantages over | __LINE__ imo. | | Crafting macros is often black magic, but using them | shouldn't be (if they're well crafted). Having an implicit | rule in your macro that it cannot be used twice in the same | line is surprising and potentially dangerous. | | Another example is where you have a macro doing lots of work, | if it needs to use a submacro multiple times that itself | needs a unique identifier, then __LINE__ is no longer | sufficient. | | __UEXP_COUNTER__ is a little more difficult to imagine a use- | case for, I'll admit (I can see it allows passing counters | around, but I can't see why a parameter couldn't do the | same). | | Again, preprocessor macros are black magic, these additions | seem a lot simpler to understand than `__VA_OPT__` (and its | predecessor `##__VA_ARGS__`) or MSVCs awful stringify | problems, as you brought up. | kazinator wrote: | __UEXP_COUNTER__ is required so that you can abstract | creating unique symbols: #define MAC(...) | WHATEVER UNIQ(X) WHATEVER | | without __UEXP_COUNTER__, you would have to pass | __EXP_COUNTER__ as an extra argument to UNIQ(X). | | You cannot make a nickname for __EXP_COUNTER__; e.g. | #define EC __EXP_COUNTER__ | | because the expansion of EC itself has its own counter! | | Using __UEXP_COUNTER__, UNIQ(X) can just do everything | necessary: obtain MAC's counter, and combine it with the | prefix X. | zabzonk wrote: | I started but couldn't finish. For example __FILE__ (and __LINE__ | and similar) certainly can be used in user-written macros, but | they evaluate to the file/line they appear at in the macro | source, not at the line the macro is invoked. | elteto wrote: | This is not correct. They evaluate at the point where they are | expanded. | | I've used them countless of times to implement "log" and | "trace" macros that show file/line. | zabzonk wrote: | I have used them countless timers myself, but not within | another macro | | Edit: Sorry I am on multiple drugs at the moment for a severe | fracture - I'm a bit confused, nurse has just been. I will | shut up now. | | To prove I am not completely out of it (though wrong), here's | one I wrote a lot earlier: | https://latedev.wordpress.com/2012/08/09/c-debug-macros/ | elteto wrote: | Sorry to hear that! Rest up and don't worry about cpp | macros for now ;) they'll still be there after you heal. | Good luck! | gpderetta wrote: | most commonly they are typically used in the definition of | the assert macro. For example: | https://github.com/lattera/glibc/blob/master/assert/assert.h | . | camel-cdr wrote: | > they evaluate to the file/line they appear at in the macro | source, not at the line the macro is invoked. | | What are you talking about? Maybe I'm missing your point, but: | | #define A __LINE__ | | A | | A | | Expands to: | | 2 | | 3 | svnpenn wrote: | and... this is why I prefer Go. No macros. | isatty wrote: | Go has even uglier half supported things like #embed. | svnpenn wrote: | Seems fine to me: | | https://godocs.io/embed | | Difference is it's just data, not executable code. I don't | understand why people need macros, when functions exist. | camel-cdr wrote: | #embed coming to c distributions near you in 2023 [0] | | [0] https://www.open- | std.org/jtc1/sc22/wg14/www/docs/n3017.htm | pjmlp wrote: | //go:generate ___________________________________________________________________ (page generated 2022-07-14 23:00 UTC)