[HN Gopher] Workarounds for C11 _Generic()
       ___________________________________________________________________
        
       Workarounds for C11 _Generic()
        
       Author : fanf2
       Score  : 48 points
       Date   : 2023-07-30 17:06 UTC (5 hours ago)
        
 (HTM) web link (www.chiark.greenend.org.uk)
 (TXT) w3m dump (www.chiark.greenend.org.uk)
        
       | AlbertoGP wrote:
       | 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?_
        
       | camel-cdr wrote:
       | 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
        
       | constantcrying wrote:
       | 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 wrote:
         | 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 wrote:
         | 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.)
        
           | maldev wrote:
           | You make it sound alot more complicated. Ignoring the library
           | functions or stuff you can include from safe coding standard
           | headers. You just do something like.                 BOOL
           | AddOverflowUnsigned(BYTE* a, BYTE* b, INT32 sizea, INT32
           | sizeb)       {          BOOL IsMsb = PlatformIsMSB();
           | BOOL IsLsb = PlatformIsLSB();                    //log error
           | if(!IsMsb && !IsLsb)            return FALSE;
           | //Early out for overflow. Assuming           if(sizea ==
           | sizeb && PlatformIsMSB())          {             BOOL AMsb =
           | (a >> ((sizea * PlatformByteSize()) - 1)) & 1;
           | BOOL BMsb = (b >> ((sizeb * PlatformByteSize()) - 1)) & 1;
           | if(AMSB && BMsb)             {                //We overflow,
           | early out.                return FALSE;             }
           | else if(sizea == sizeb && PlatformIsLSB())             //Code
           | here.               //We add using bitwise operators so we
           | can do it on any size.          //SUM = A XOR B XOR CARRY,
           | return in A          //Impliment algo here.
           | return TRUE;       }            int main()       {
           | UINT64 a = 69;          UINT64 b = 420;          if(sizeof(a)
           | !          return AddOverflowUnsinged(&a, &b, sizeof(a),
           | sizeof(b));       }
           | 
           | But they have this in the STD now and also all the secure
           | coding libs have this in it's header as a basic function.
           | There's also the easier implementation of just adding a check
           | to see if it will overflow, rather than add in the function.
           | 
           | You also only have a few you can hardcode if you don't do it
           | generic, since all will go back to the primitives of uint8,
           | uint16 ....
        
       | ajross wrote:
       | 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 wrote:
         | 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 wrote:
         | Macros and #ifdef do not solve the problems that _Generic is
         | used for. _Generic is for static polymorphism, not for target
         | or build configuration.
        
           | mostlylurks wrote:
           | Macros are perfectly capable of static polymorphism. That is
           | one of their primary use cases. _Generic is simply an
           | alternative mechanism for static polymorphism, with some
           | extra capabilities and some limitations in comparison to your
           | typical macro.
        
       | sigsev_251 wrote:
       | 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...
        
       | nstbayless wrote:
       | 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 wrote:
         | Heh, this was my first thought as well, and it does indeed
         | compile and work (with GCC 12.2, anyway).
        
       | dottedmag wrote:
       | 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.
        
       | Rexxar wrote:
       | Not directly related but does some c/c++ compiler implement a
       | combination of flags that create a sort "c with templates"
       | version of C ?
        
         | Gibbon1 wrote:
         | I think it'd be more C like to add types as first class, tagged
         | unions, and phat pointers.
        
         | constantcrying wrote:
         | Just use C++ with just templates?
        
           | jimbob45 wrote:
           | That would be my advice too. What was the point of "You don't
           | pay for what you don't use" if nobody's going to use it?
        
           | [deleted]
        
         | LexiMax wrote:
         | 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.
        
         | cmovq wrote:
         | 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.
        
       ___________________________________________________________________
       (page generated 2023-07-30 23:00 UTC)