The Preprocessor Iceberg Meme
jadlevesque.github.io
jadlevesque.github.io
It's worth nothing that a macro system plus basic reflection is where the real power of macros lies at.
Templates, man.
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>
Templates it must be.
A good perspective.
That still doesn't help if you want the fields names.
But you are right that C++ templates are a bad functional programming language with duck typing.
The root cause is that template expansion is duck-typed. 'Concepts' are supposed to fix that, I heard.
#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 40What's C++ and what's regex?
(That's why it's great that Haskell and other languages in that family allow you to define your own operators, instead of eg re-using bit-shifting for IO.)
(Btw, parsing C++ is already Turing complete.)
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.
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.
That is what is missing piece of the puzzle.
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> #define end_element() \
while (0); \
do { \
if (xmlTextWriterEndElement (xo) == -1) { \
xml_error ("xmlTextWriterEndElement"); \
} \
} while (0)
Talking about the first while (0);(The condition is optimized away by the big three: msvc, clang, and gcc)
if … else {} you could omit the ;
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.
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.
Even only moderately complex PP metaprogramming requires multiple rounds of wrapping, so I'm not sure it is such a big burden.
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.
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.
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.
#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.
__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. #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 6IMO the best solution if you want to avoid an extra indirection to inject some state, would be preprocessor variables that you can assign to in a macro expansion. Procedural preprocessor code basically. But the preprocessor doesn't work like that.
I would do the opposite: keep halving the ping interval until they notice.
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.
__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.
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.
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.
Using a symbolic debugger with macros is just another facet of the slow moving disaster of macro usage.
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.
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 :-)
In the D community, we also strongly discourage operator overloading for any purpose other than creating arithmetic types.
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.
Gladly using iostreams since 1993.
Unless you define "community" as those that do not share your point of view.
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.
#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.
Wow, I have no idea why this would ever be done.
#if SOME_STUFF
evaluating to 0 if SOME_STUFF isn't definedYou'd never literally write
#if static_cast<bool>(-1)
but it could be the result of a macro expansion.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.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".
edit: it is basically loop unrolling.
[0] https://jadlevesque.github.io/PPMP-Iceberg/explanations#sing...
~~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.~~
BTW: I don't think the titles of each entry are particularly clickbait-y.
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.
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.If you somehow force a bool to be a different value (e.g. `*(int*)(&myBool) = 7`), that's UB.
The C/C++ preprocessor is probably the esoteric programming language that see the most real world use. Or would that be C++ template metaprogramming...?
(i know that you can have pre-processor abuse in C++ too, but that's not common practice)
[0] https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3017.htm
Difference is it's just data, not executable code. I don't understand why people need macros, when functions exist.
I’ve used them countless of times to implement “log” and “trace” macros that show file/line.
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/
What are you talking about? Maybe I'm missing your point, but:
#define A __LINE__
A
A
Expands to:
2
3