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