[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)