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