6 ms·
For your level 2 code, `uint64_t data[];` is wrong for types whose alignment is greater than `uint64_t`, and also wasteful for types whose alignment is smaller
by o11c 1y ago
For your level 2 code, `uint64_t data[];` is wrong for types whose alignment is greater than `uint64_t`, and also wasteful for types whose alignment is smaller (for example, under an ilp32 ABI on 64-bit architectures).
For your level 3 code, it should be `int main() { List(Foo) foo_list = {NULL};`
Note that working around a lack of `typeof` means you can't return anything. Also, your particular workaround allows `const`ness errors since `==` is symmetrical.
You can't safely omit `payload` since you need it to know the correct size. Consider a `List(int64_t)` and you try to add an `int32_t` to it - this should be fine, but you can't `sizeof` the `int32_t`. Your code is actually lacking quite a bit to make this work.
=====
There are 2 major limitations to generics in C right now:
* Delegating to a vtable (internal or external) is limited in functionality, since structs cannot contain macros, only functions.
* Delegating to an external vtable (mandatory to avoid overhead) means that you have to forward-declare all of the types you'll ever use a vtable with. So far the best approach I've found is to declare (but not define) static functions in the same forwarding header I declare the typedefs in; note that GCC and Clang differ in what phase the "undefined static" warning appears in for the case where you don't actually include that particular type's header in a given TU.
(think about writing a function that accepts either `struct SizedBuffer {void *p; size_t len;};` or `struct BoundedBuffer {void *begin; void *end;};`, and also const versions thereof - all from different headers).
- EPWN3D 1y agoI would love for `union`s to be federated, that is, a type could declare itself as thought it was part of a union with another type, without having to pre-declare all possible types in one place.
- o11c 1y agoFor layout-compatible types, you can often just include a `_base` member in each child. Maybe twice (once named and once unnamed) to avoid excess typing - I don't understand the common-initial-subsequence rule but people do this enough that compilers have to allow it.
- manwe150 1y agoI don't think compilers typically allow that: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=14319 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=14319
- Joker_vD 1y agoCan't you just declare anonymous unions whenever? E.g. struct foo { ... }; struct bar { ... }; struct bar check_this_out(struct foo foo) { return ((union { struct foo foo; struct bar bar; }){ .foo = foo}).bar; } struct bar *also_this(struct foo *foo) { union { struct foo foo; struct bar bar; } *tmp = (void*)foo; return &tmp->bar; }
- rectang 1y ago> Delegating to an external vtable (mandatory to avoid overhead) means that you have to forward-declare all of the types you'll ever use a vtable with. We went down the rabbit hole of writing a compiler for this as part of a project I used to work on (Apache Clownfish[1], a subproject of the retired Apache Lucy project). We started off parsing .h files, but eventually it made sense to create our own small header language (.cfh "Clownfish Header" files). Here's some generated code for invoking the CharBuf version of the "Clone" method defined in parent class "Obj": typedef cfish_CharBuf* (*CFISH_CharBuf_Clone_t)(cfish_CharBuf* self); extern uint32_t CFISH_CharBuf_Clone_OFFSET; static inline cfish_CharBuf* CFISH_CharBuf_Clone(cfish_CharBuf* self) { const CFISH_CharBuf_Clone_t method = (CFISH_CharBuf_Clone_t)cfish_obj_method( self, CFISH_CharBuf_Clone_OFFSET ); return method(self); } Usage: cfish_CharBuf *charbuf = cfish_CharBuf_new(); cfish_CharBuf *clone = CFISH_CharBuf_Clone(charbuf); We had our reasons for going to these extremes: the point of Clownfish was to provide a least-common-denominator object model for bindings to multiple dynamic languages (similar problem domain to SWIG), and the .cfh files also were used to derive types for the binding languages. But there was truly an absurd amount of boilerplate being generated to get around the issue you identify. This is why almost everybody just uses casts to void* for the invocant, skipping type safety. [1] https://github.com/apache/lucy-clownfish https://github.com/apache/lucy-clownfish
- zem 1y agoi am firmly of the opinion that compiling to c is a better route than doing clever c tricks to sort of get what you want. the compiler can be pretty minimal and as you note it pays for itself.
- jiggawatts 1y agoThere’s some prior work called CFront. It implements a superset of C that’s just an “increment”. I think it’s worth looking into, it might take off one day!
- mingodad 1y ago
- n_plus_1_acc 1y agoThis is also problematic, because there might be padding and the calculated size might be too small: `malloc(sizeof(*node) + data_size);`
- o11c 1y agoThere's no a problem with the author's current code, since the padding is already included in the node size, but it would be a problem after doing alignment more intelligently.
- kccqzy 1y ago> it should be `int main() { List(Foo) foo_list = {NULL};` In C `int main()` means the function takes an unknown number of arguments. You need `int main(void)` to mean it doesn't take any arguments. This is a fact frequently forgotten by those who write C++.
- tedunangst 1y agoThis is incorrect. In a function definition, an empty list means it takes no parameters. 6.7.5.3 Function declarators > 14. An empty list in a function declarator that is part of a definition of that function specifies that the function has no parameters.
- s3graham 1y agoAs you surely know if you're quoting the standard, it depends on which standard!
- gpderetta 1y agoI believe that since C23 foo() is now a nullary function. As this is the last approved standard and it supersedes all previous standards, it is technically correct to say that de-jure this is what the (unqualified) C standard mandates. Of course de-facto things are more nunanced.
- el_pollo_diablo 1y agoC23 does not change anything in this situation, because we are talking about the definition of main(), not a forward declaration. More details here: https://news.ycombinator.com/item?id=38729278#38732366 https://news.ycombinator.com/item?id=38729278#38732366
- fuhsnn 1y agoIn what situation fn() doesn't mean fn(void) under C23?
- deleted 1y ago[deleted]
- KingLancelot 1y ago[dead]