top | item 36933345

Workarounds for C11 _Generic()

74 points| fanf2 | 2 years ago |chiark.greenend.org.uk | reply

53 comments

order
[+] camel-cdr|2 years ago|reply
Tiny C compiler actually has a "bug" in not implementing the "big bug".

That is, the following expressions are cleanly compiled without any errors:

    _Generic(0, float: s/<[^>]*>/ /g, int: 1),
    _Generic(0, float: now the thing about this tcc implementaion is, int: 1),
    _Generic(0, float: that it just skips until the next comma, int: 1),
    _Generic(0, float: keeping track of parentheses nesting., int: 1),
    _Generic(0, float: [[ Suprizingly it doesnt validate which parentheses)), int: 1),
    _Generic(0, float:  {{{so this bullshit is possible))), int: 1,
             default: #else #define this is great isnt it?);
See: https://godbolt.org/z/o7jne7hWM
[+] nstbayless|2 years ago|reply
Will this work?

  define string_length(x) _Generic(x,        \
    const char *            : strlen((const char*)(const void*)x),           \
    struct MyStringBuffer * : ((const MyStringBuffer*)(const void*)x)->length)
[+] kelnos|2 years ago|reply
Heh, this was my first thought as well, and it does indeed compile and work (with GCC 12.2, anyway).
[+] AlbertoGP|2 years ago|reply
At the bottom of the page lemonade is made:

> Apart from the most obvious answer (that it’s useful for things exactly like <tgmath.h>), the best thing I’ve thought of to do with _Generic is to use it for deliberate error checking.

> The annoyance in all the previous sections was that it was very hard to avoid compile errors, when we wanted our code to actually compile, run and do something useful. But if what we wanted was to provoke compile errors on a hair-trigger basis, then perhaps we could use it for that more reliably?

[+] dottedmag|2 years ago|reply
Somehow C++ (and now C) standardization very often ends up with constructs that require a lot of ugly hacks in the code that uses them.
[+] ajross|2 years ago|reply
According to this the "big bug" is that... _Generic works mostly like a macro and expands code that the compiler sees. That seems like a little weak, macros have been doing this forever via a mere extra level of indirection.

So sure, "(x)->length" might not be valid syntax in all configurations the compiler might see. But "LENGTH(x)" is, e.g.:

   #if X_MIGHT_BE_MYSTRINGBUFFER
   #define LENGTH(x) ((x)->length)
   #else
   #define LENGTH(x) 0 /* anything that converts to the output type will do */
   #endif
This is a routine pattern seen everywhere in C. The Linux kernel is filled with it, e.g. field accessors or arch-specific functions that are stubbed out when not needed, etc...

Is this as clean as a full-on generic typesystem? No. But it's C, it shows a weirdness that you have to handle manually, and you do it the same way we've been doing it in C for decades. Not a new problem, doesn't need a new solution. It's C!

[+] kelnos|2 years ago|reply
I think you've misunderstood the purpose of _Generic.

Your #if statement will select exactly one implementation for every single site of the use of LENGTH() in the codebase, depending on the value of X_MIGHT_BE_MYSTRINGBUFFER at compile-time.

_Generic() allows you to have both implementations available, and different implementations can be selected at the call site depending on the type of the argument passed.

[+] fanf2|2 years ago|reply
Macros and #ifdef do not solve the problems that _Generic is used for. _Generic is for static polymorphism, not for target or build configuration.
[+] kazinator|2 years ago|reply
I would have designed the feature like this:

  _Generic(<expr>, type1 : ( expr1 ), type2 : ( expr2 ), ... default : ( expr ))
Here, the parentheses shown in this phrase pattern are required.

The implementation would only parse and semantically analyze the expression of the matching type. For the others, the ( expr ) would be treated as a token sequence to be skipped, which has to contain valid tokens, and balancing parentheses, square brackets and braces.

E.g.

   char *p = "foo"

   _Generic(p : int : ("foo" ++ / ? ([]&xyz) { ; } ),
                char * : (p[0]))

Here, the type of p doesn't match int, and so the interior token sequence of ("foo" ++ / ? ([]&xyz) { ; } ) would just be scanned to check for balancing parentheses, brackets and braces, which allows the parser to locate the next clause in the association list.
[+] Dylan16807|2 years ago|reply
That would allow you to put syntactically invalid code into the expressions, but is that a useful feature? It's not very hard to have valid syntax in a situation like this. The main issue here is type enforcement.
[+] reichstein|2 years ago|reply
Just admit that it's pattern matching, and introduce a new variable for each match: ``` _Generic(expr0, type1 id1?: expr1, ... typeN idN?: exprN, default id0?: exprDefault ) ``` Every identifier is optional, but if you include `idX`, it gets bound to the value of `expr0` with type `typeX` in the following expression, or with the type of `expr0` for the default case.

Then you can safely refer to the value at its now-known type, even if `expr0` was not a variable to begin with.

[+] constantcrying|2 years ago|reply
The only reason I can imagine for this behaviour is that the compiler/standard writers did not want it.

Maybe C just shouldn't include generics. Especially not as part of the macro layer.

[+] quelsolaar|2 years ago|reply
As a member of the WG14 I can tell you that _Generic does cause a fair bit of issues and complications in the language, and as a user of C i think _Generic is bad, because it mostly useful for confusing users about what code does so, Yes C would be better off without _Generic. Please don't use it.
[+] mananaysiempre|2 years ago|reply
The macros in tgmath and more recently stdbit show why those could be necessary.

If you have a set of functions for addition with overflow detection, say add_overflow{i,l,ll}, and you have a pair of ptrdiff_t’s or int32_t’s or whatnot that you know are standard integer types, and you want to use the appropriate add_overflow* function, can you do it?

With _Generic you can. Without it I think you’re stuck providing separate functions for every integer typedef in the standard library and then requiring all library authors to do the same for both integer typedefs and integer-accepting functions that they define.

(_Generic is not part of the macro level, that’s why the semantic-checking issues discussed in the article even arise. The C preprocessor can still be implemented as a separate binary that doesn’t understand C itself, even in C23.)

[+] ActorNightly|2 years ago|reply
Generics are fine. Its useful to have some introspection into the data that compiler has. Thats the whole point of macros in the first place.

The behavior with _Generic is basically emergent behavior from implementation of macro processors. Macro replacements occur prior to actual compilation, so no compiler context exists for x, as such all code paths must be valid.

Its much easier to require the programmer typecast x in the replacement value then start trying to shoehorn the compiler context into macro processors.

[+] j16sdiz|2 years ago|reply
This is bad for non-compiler tools.

Without it, we can parse the preprocessed source code.

With the enhancement proposed, we need to do half the compiler's work.

In tools like IDE, we want quick (sub millisecond) feedback for most edits

[+] Rexxar|2 years ago|reply
Not directly related but does some c/c++ compiler implement a combination of flags that create a sort "c with templates" version of C ?
[+] cmovq|2 years ago|reply
Templates are so intertwined with the C++ type system such that bringing "just templates" to C would require also bringing in a bunch of C++ features if you want templates to behave like they do in C++. Just for starters, you would need name mangling and/or function overloading.
[+] LexiMax|2 years ago|reply
If C wanted a form of generics, it could likely do much better than templates. Concepts might've improved things, but I'm stuck on C++17 right now so I can't speak with experience.
[+] Gibbon1|2 years ago|reply
I think it'd be more C like to add types as first class, tagged unions, and phat pointers.
[+] sigsev_251|2 years ago|reply
It's amazing how much power _Generic, typeof, typeof_unqual, auto and empty brace initializers can give to the compile time of C (that we already enjoy in C++ through templates, constexpr, auto and friends). Now, if only we had a way to write generic code in a reliable way...
[+] hgs3|2 years ago|reply
It sounds like the author misunderstands the purpose of _Generic. The author wants it to behave like pattern matching in ML languages, but that is not its purpose. The purpose of generic selection was to introduce function overloading [1] into C without breaking ABI compatibility.

[1] https://en.wikipedia.org/wiki/Ad_hoc_polymorphism

[+] teo_zero|2 years ago|reply
The "big bug" indeed limits the possibilities of _Generic. But if you really want to abuse it beyond its intended scope, I think you can simply add some casting, and not the gimmicks with nested _Generic.

  #define length(x) _Generic(x, \
    char * : strlen((char*)(x)), \
    String * : ((String*)(x))->len \
  )