Show HN: Replace printf() with cool generic print, almost like Python/JS
github.com
github.com
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.gcc's compile-time expression chooser is also severely limited compared to clang.
>>> f"{name=}"
'name=Rodrigo'It wasn’t added until Python 3.8 (f-strings were new in 3.6).
It appears to work just fine there. Though, once again I question my decision for a light-blue background for my terminal.
As for your terminal blue background it looks gorgeous, maybe you just need to tweak the palette a little bit to make sure at least 16 basic colors are contrast enough. Btw what is the meaning of :28 in your prompt? Is this amount of files changed or a revision number?
The number is the number of revisions. Helps me track how "old" different copies of the same repo is. The star next to it denotes changes (in this case the a.out)
The only other things this prompt does right now is show an error code if a command exits with one, and changes to "can't miss it" red when run as root. For me, this is a minimal prompt, I oscillate between a lot more detail, and something closer to a standard installation.
Edit: looking more into it, the header is rather unhygienic: if I include print.h from multiple .c files I break ODR, which is undefined behavior. You have variable definitions in a header which is not good. You should guard those variables with a preprocessor define (something like DECLARE_PRINT_VARIABLES or similar) so I can then #define that to 1 in one and only one .c file and then we don’t break ODR.
In my header-only library I have different tricks to detect literal POD types. But portable. In your case unportable is fine.
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.