[HN Gopher] Show HN: Replace printf() with cool generic print, a...
___________________________________________________________________
Show HN: Replace printf() with cool generic print, almost like
Python/JS
Author : exebook
Score : 16 points
Date : 2021-02-23 22:02 UTC (58 minutes ago)
(HTM) web link (github.com)
(TXT) w3m dump (github.com)
| exebook wrote:
| I was reading GCC builtins documentation and suddenly realized I
| can make a generic print function at which you can throw
| variables of arbitrary types and sizes, even arrays! It is super
| cool, almost like console.log() in JavaScript or print in Python.
| dietrichepp wrote:
| So... I've done this. I ported the Python formatting system to C.
| I did not use any GNU extensions like "a..." in macros or any of
| the __builtin. It turns out you can do this with regular variadic
| macros and _Generic, which is standard C these days. By "standard
| C" I am talking about C11. strbuf_format(
| &buf, "Hello, {1}! 2 + 5 = {0} {{brace}}\n", 2 + 5,
| "World"); strbuf_format(&buf, "Number = {:#010x}\n",
| 0xfeed); strbuf_format(&buf, "Grouped = {:,}\n",
| 314159265358979323);
|
| This works. It is type-safe. I have not released it because I
| think it's a bit... well, painful to use. I'm sure my
| implementation is a bit different from the one posted, but the
| key insights are:
|
| 1. You can write a variadic macro that figures out how many
| arguments it was passed in __VA_ARGS__,
|
| 2. You can use this variadic macro to paste the number of
| arguments into the name of another macro which is invoked using
| the ## preprocessor concatenation operator,
|
| 3. You can dispatch on the argument types using _Generic.
|
| The limitations are:
|
| - You have to pick a maximum number of arguments and hard-code
| that maximum into your library (in order for #1 to work above).
|
| - Macro-expansion makes the error messages a bit crazy and hard
| to read, like C++, but worse.
|
| - _Generic is rather user-hostile when there is an error.
|
| - Partitioning integral types with _Generic is probably the right
| way to do things. Unlike C++, there is no implicit casting. It is
| not obvious how to partition the integral types. I ended up using
| char, short, int, long, and long long, plus unsigned versions of
| each, and finally signed char (because char != signed char).
|
| I will say that my version is probably a little more efficient at
| the call site, since it packs the argument types as bits into
| integer arguments. For example, it's something like this:
| format("x = {} + {}", 2, 3u); // Turns into something
| like... format_impl_("x = {} + {}", // 2 =
| count 2 | (kFormatInt << 8) | (kFormatUnsigned <<
| 12), 2, 3u);
|
| So the type information for up to 14 arguments is packed into one
| additional argument, on 64-bit systems. I use code generation for
| the macros to handle as many arguments as I like, and there is
| also 32-bit support.
| im3w1l wrote:
| Looks very cool! How does it work, and how fast is it?
| exebook wrote:
| Did not check the speed. I want to use it during
| debugging/developing so speed was not something I was thinking
| about.
|
| It is not a very elegant solution thanks to limitations of
| preprocessor __VAR_ARGS__. Basically there is a compiler
| builtin function __builtin_types_compatible_p() that checks if
| the argument is of a given type, then there is another builtin
| __builtin_choose_expr() that can do something with the result
| of the former. Using those two builtins I construct an array of
| type information and pass it to the actual printing function
| which is using <stdarg.h> and the array of type information to
| print stuff.
___________________________________________________________________
(page generated 2021-02-23 23:01 UTC)