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.
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.
Workarounds for C11 _Generic()
41–50 of 58 posts
Re: Workarounds for C11 _Generic()
#42Earlier quoted context omitted.
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.
_Generic allows for more complex assertions in static_assert which is really useful for correctness (For example, your embedded chip vendor offers an API where uint8_t is used to transfer bytes using a protocol, so you have to assert that uint8_t is either char or unsigned char in order to prove that there will be no strict aliasing violation during pointer casting). Also, it's useful in order to implement the length…
Re: Workarounds for C11 _Generic()
#43Earlier quoted context omitted.
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.
To prove your assertion, would you mind defining a length() function via macros that works with both a char * and a pointer to a struct {int len; char *data}, as the fine article explained?
_Generic does ad-hoc polymorphism via type-based dispatch in order to implement function overloading.
Macros are more like a limited form of parametric polymorphism, in that they are polymorphic up to what is allowed by the operations they apply to their arguments. In C that typically means relying on implicit or explicit coercions, or C’s mildly overloaded operators.
Re: Workarounds for C11 _Generic()
#44Earlier quoted context omitted.
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 sep…
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 AM…
As the main option it's extremely silly, though, when the entirety of the function on e.g. x86-64 could be (using the GCC signature but MS calling convention and syntax for according to your apparent preference)
; bool add_overflowll(long long x, long long y, long long *result)
add_overflowll PROC
add rcx, rdx
seto eax
mov qword ptr [r8], rcx
ret
add_overflowll ENDP
for a total of no loops, no conditional branches, and three instructions with no branches at all when inlined (not shown because I don't remember the MS inline assembly syntax). Why check beforehand when the CPU does the check for you on every arithmetic operation and all you need to do is to ask it for the result? (Unless your CPU is RISC-V because RISC-V.)And of course the newfangled standard functions are size-dependent (more like mine than yours), so either you still need _Generic or essentially equivalent compiler-specific magic, or you have to introduce an ABI dependency on which integer type your ptrdiff_t or ssize_t or off_t or whatever typedef you got from a random place in a library or OS header actually ends up being.
Re: Workarounds for C11 _Generic()
#45Earlier quoted context omitted.
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 sep…
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 AM…
add rdi, rsi
setc eax
mov [rdx], rdiRe: Workarounds for C11 _Generic()
#46According 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 #defin…
Macros and #ifdef do not solve the problems that _Generic is used for. _Generic is for static polymorphism, not for target or build configuration.
No, but they solve the specific "big bug" described in the article very well. Use _Generic for what it's good for. Use macros to fill in the gaps (in this case build/preprocessor configuration differences that change the syntax used in the RHS of the generic expansion).
Again, this is C. You want fancy type systems, you know where to find them. Don't ask for them here, we don't want them.
Re: Workarounds for C11 _Generic()
#47Earlier quoted context omitted.
> but is that a useful feature? It would solve the usability issues experienced by Simon Tatham, described in his article. For instance, if one of the clauses has x->foo, it wouldn't matter that the actual x argument in a given instance is char *, that cannot be dereferenced as x->foo. It would be up to the programmer to test every one of the supported cases of their _Generic construct. One way to avoid problems woul…
> It would solve the usability issues experienced by Simon Tatham, described in his article. In a very overkill way, though. You can solve the issue in the article by parsing the expression but not doing anything with it. You don't need to allow invalid syntax for that problem. So to be clear, I'm asking specifically about whether it's useful to allow invalid syntax inside _Generic. What does that allow in practical…
I don't know that about the implementation which parses the dead code. Implementing it could be troublesome in some way. In any compiler that checks constraints while parsing, implementing parsing without constraint checking means going to all those places and checking a flag.
Actually, it occurs to me that none of this is necessary. How the construct can work is by pretending that the argument in the dead cases has the type of the left side of the association list entry:
So if we have
_Generic(x,
char * : puts(x),
dev * : puts(x->devname));
which is invoked with x being "abc", the dead dev * clause should be parsed and type checked under the pretense that there is a dev *x declaration in scope. And not that x is of type char *.The implementation should be, anyway, generating a hidden local variable (gensym) to take the value of the expression.
We can imagine a code transformation like this (using GCC brace syntax):
_Generic(x,
char * : ({ char *__g0025 = x; puts(__g0025); }),
dev * : (({ dev *__g0025 = x; puts(__g0025->devname); }))
Now here, we just have to suppress type checking of the dev *__g0025 = x; declaration where the only problem is that x is incompatible with __g0025. (That declaration wouldn't even have to be source code: it can be injected at the AST level.)While this is all cool, my bracket counting idea is much simpler and more robust toward cases like different syntactic extensions being used from different compilers or other unforseen snags.
Re: Workarounds for C11 _Generic()
#48Earlier quoted context omitted.
Macros and #ifdef do not solve the problems that _Generic is used for. _Generic is for static polymorphism, not for target or build configuration.
> Macros and #ifdef do not solve the problems that _Generic is used for. No, but they solve the specific "big bug" described in the article very well. Use _Generic for what it's good for. Use macros to fill in the gaps (in this case build/preprocessor configuration differences that change the syntax used in the RHS of the generic expansion). Again, this is C. You want fancy type systems, you know where to find them.…
But the problem here is caused by the type system being overly aggressive. If it wasn't trying to force the wrong type onto unused code, everything would work fine.
Re: Workarounds for C11 _Generic()
#49Earlier quoted context omitted.
> Macros and #ifdef do not solve the problems that _Generic is used for. No, but they solve the specific "big bug" described in the article very well. Use _Generic for what it's good for. Use macros to fill in the gaps (in this case build/preprocessor configuration differences that change the syntax used in the RHS of the generic expansion). Again, this is C. You want fancy type systems, you know where to find them.…
> You want fancy type systems, you know where to find them. Don't ask for them here, we don't want them. But the problem here is caused by the type system being overly aggressive. If it wasn't trying to force the wrong type onto unused code, everything would work fine.
Re: Workarounds for C11 _Generic()
#50Earlier quoted context omitted.
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…
> Macro replacements occur prior to actual compilation, so no compiler context exists for x, as such all code paths must be valid. How is it throwing an error like "invalid type argument of ‘->’" if there's no compiler context? And why does an error like that get in the way of macro replacements? (That might be the wrong message but the article says you at least get an equivalent.)