Live data from Hacker News

Generic Containers in C: Vec

uecker.codeberg.page

41–50 of 91 posts

Re: Generic Containers in C: Vec

#41
post #22

Many C programmers need proper generic programming mechanisms (perhaps something like Zig's comptime) in C, but macros are the worst possible approach, and they don't want to switch to a different language like C++. As a result, they struggle with these issues. This is what I think the standardization committee should focus on, but instead, they introduced _Generic.

The most insulting thing about _Generic is the name. Really? _Generic? For a type-based switch with horrific syntax? What were they thinking... That said, generic programming in C isn't that bad, just very annoying. To me the best approach is to write the code for a concrete type (like Vec_int), make sure everything is working, and then do the following: A macro Vec(T) sets up the struct. It can then be wrapped in a…

There are less annoying ways to implement this in C. There are at least two different common approaches which avoid having macro code for the generic functions:

The first is to put this into an include file

  #define type_argument int
  #include 
Then inside vector.h the code looks like regular C code, except where you insert the argument.

  foo_ ## type_argument ( ... )
The other is to write generic code using void pointers or container_of as regular functions, and only have one-line macros as type safe wrappers around it. The optimizer will be able to specialize it, and it avoids compile-time explosion of code during monomorphization,

I do not think that templates are less annoying in practice. My experience with templates is rather poor.

Re: Generic Containers in C: Vec

#42

It's amazing how many people try to write generic containers for C, when there is already a perfect solution for that, called C++. It's impossible to write generic type-safe code in C, and this version resorts to using GCC extensions to the language (note the ({…}) expressions). For those afraid of C++: you don't have to use all of it at once, and compilers have been great for the last few decades. You can easily por…

My problem with C++, and maybe this is just me, is RAII. Now, Resource Aquisition Is Initialization is correct, but the corollary is not generally true, which is to say, my variable going out of scope does not generally mean I want to de-aquire that resource. So, sooner or later, everything gets wrapped in a reference counting smart pointer. And reference counting always seemed to me to be a primitive or last-resort…

> my variable going out of scope does not generally mean I want to de-aquire that resource.

But it does! When an object goes out of scope, nobody can/shall use it anymore, so of course it should release its (remaining) resources. If you want to hold on the object, you need to revisit its lifetime and ownership, but that's independent from RAII.

Re: Generic Containers in C: Vec

#43
post #41

Earlier quoted context omitted.

The most insulting thing about _Generic is the name. Really? _Generic? For a type-based switch with horrific syntax? What were they thinking... That said, generic programming in C isn't that bad, just very annoying. To me the best approach is to write the code for a concrete type (like Vec_int), make sure everything is working, and then do the following: A macro Vec(T) sets up the struct. It can then be wrapped in a…

There are less annoying ways to implement this in C. There are at least two different common approaches which avoid having macro code for the generic functions: The first is to put this into an include file #define type_argument int #include Then inside vector.h the code looks like regular C code, except where you insert the argument. foo_ ## type_argument ( ... ) The other is to write generic code using void pointer…

Those techniques being less annoying is highly debatable ;). Working with void* is annoying, header includes look quite ugly with the ## concatenation everywhere or even a wrapper macro. It also gets much worse when you need to customize the suffix (because type_argument is char* or whatever).

Sometimes the best option is an external script to instantiate a template file.

Re: Generic Containers in C: Vec

#44
post #41

Earlier quoted context omitted.

There are less annoying ways to implement this in C. There are at least two different common approaches which avoid having macro code for the generic functions: The first is to put this into an include file #define type_argument int #include Then inside vector.h the code looks like regular C code, except where you insert the argument. foo_ ## type_argument ( ... ) The other is to write generic code using void pointer…

Those techniques being less annoying is highly debatable ;). Working with void* is annoying, header includes look quite ugly with the ## concatenation everywhere or even a wrapper macro. It also gets much worse when you need to customize the suffix (because type_argument is char* or whatever). Sometimes the best option is an external script to instantiate a template file.

It may be debatable, but I would say C++'s template syntax is not nicer. I do not think working with void pointers is annoying, but I also prefer the container_of approach. The ## certainly has the limitation that you need to name things first, but I do not think this much of a downside.

BTW, here is some generic code in C using a variadic type. I think this quite nice. https://godbolt.org/z/jxz6Y6f9x

Running a program for meta programming are always a possibility, and I would agree that sometimes the best solution.

Re: Generic Containers in C: Vec

#45
post #16

The post is more of a quick-n-dirty (and rather trivial) proof of concept as the code includes only sporadical checks for allocation errors and then adds a hand-wavy disclaimer to improve it as needed. E.g. in production code this if (!vec_ptr) // memory out abort(); for (int i = 0; i should really be if (!vec_ptr) // memory out abort(); for (int i = 0; i but it doesn't really roll of the tongue.

If this is in a library code, then I tend to disagree. As an user of a library, I would rather be able to handle errors the way I want, I do not want the library to decide this for me, so just return an error value, like "VEC_ERR_NOMEM", or whatever.

Re: Generic Containers in C: Vec

#46
post #41

Earlier quoted context omitted.

The most insulting thing about _Generic is the name. Really? _Generic? For a type-based switch with horrific syntax? What were they thinking... That said, generic programming in C isn't that bad, just very annoying. To me the best approach is to write the code for a concrete type (like Vec_int), make sure everything is working, and then do the following: A macro Vec(T) sets up the struct. It can then be wrapped in a…

There are less annoying ways to implement this in C. There are at least two different common approaches which avoid having macro code for the generic functions: The first is to put this into an include file #define type_argument int #include Then inside vector.h the code looks like regular C code, except where you insert the argument. foo_ ## type_argument ( ... ) The other is to write generic code using void pointer…

An idea I had was to implement a FUSE filesystem for includes, so instead of the separate `#define type_argument` (and `#undef type_argument` that would need to follow the #include), we could stick the type argument in the included filename.

   #include 
   #include 
The written `vector.h(type_argument)` file could just be a regular C header or an m4 file which has `type_argument` in its template. When requesting `vector.h(int32_t)` the FUSE filesystem would effectively give the output of calling `gcc -E` or `m4` on the template file as the content of the file being requested.

Eg, if `vector.h(type_argument)` was an m4 file containing:

    `#ifndef VECTOR_'type_argument`_INCLUDED'
    `#define VECTOR_'type_argument`_INCLUDED'

    typedef struct `vector_'type_argument {
        size_t length;
        type_argument values[];
    } `vector_'type_argument;
 
    ...
    #endif
Then `m4 -D type_argument=int32_t vector.h(type_argument)` gives the output:

    #ifndef VECTOR_int32_t_INCLUDED
    #define VECTOR_int32_t_INCLUDED
    
    typedef struct vector_int32_t {
        size_t length;
        int32_t values[];
    } vector_int32_t;
    
    ...
    #endif
But the idea is to make it transparent so that existing tools just see the pre-processed file and don't need to call `m4` manually. We would need to mount each include directory that uses this approach using said filesystem. This shouldn't require changing a project's structure as we could use the existing `include/` or `src/` directory as input when mounting, and just pick some new directory name such as `cfuse/include` or `cfuse/src`, and mount a new directory `cfuse` in the project's root directory. The change we'd need to make is in any Makefiles or other parts of the build, where instead of `gcc -Iinclude` we'd have `gcc -Icfuse/include`. Any non-templated headers in `include/` would just appear as live copies in cfuse/include/, so in theory this could work without causing anything to break.

Re: Generic Containers in C: Vec

#47

Earlier quoted context omitted.

My problem with C++, and maybe this is just me, is RAII. Now, Resource Aquisition Is Initialization is correct, but the corollary is not generally true, which is to say, my variable going out of scope does not generally mean I want to de-aquire that resource. So, sooner or later, everything gets wrapped in a reference counting smart pointer. And reference counting always seemed to me to be a primitive or last-resort…

Your problem is not with RAII, but with reference counting, which you correctly identified should be the last resort, not the default; at least for the applications typically written in C++.

Why should reference counting be a last resort?

Re: Generic Containers in C: Vec

#48
post #34
post #33

Earlier quoted context omitted.

I'm currently at a crossroads: C++ or Zig. One is very popular with a large community, amazing projects, but has lots of ugly design decisions and myriad rules you must know (this is a big pain, it seems like even Stroustrup can't handle all of them). The other is very close to what I want from C, but it's not stable and not popular.

Why not C? Its only real issue is that people will constantly tell you how bad it is and how their language of choice is so much better. But if you look at how things work out in practice, you can usually do things very nicely in C.

My choice in this situation is indeed C, but every once in a while I hit a problem that makes me yearn for better metaprogramming.

Perfect hashing that you’d ideally use two different approaches for depending on whether the platform has a cheap popcount (hi AArch32), but to avoid complicating the build you give up and emulate popcount instead. Hundreds of thousands of lines of asynchronous I/O code written in a manual continuation-passing style, with random, occasionally problematic blocking synchronization sprinkled all over because the programmer simply could not be bothered anymore to untangle this nested loop, and with a dynamic allocation for each async frame because that’s the path of least resistance. The intense awkwardness of the state-machine / regular-expression code generators, well-developed as they are. Hoping the compiler will merge the `int` and `long` code paths when their machine representations are identical, but not seeing it happen because functions must have unique addresses. Resorting to .init_array—and slowing down startup—because the linker is too rigid to compute this one known-constant value. And yes, polymorphic datastructures.

I don’t really see anybody do noticeably better than C; I think only Zig and Odin (perhaps also Hare and Virgil?) are even competing in the same category. But I can’t help feeling that things could be much better. Then I look at the graveyard of attempted extensions both special-purpose (CPC[1]) and general (Xoc[2]) and despair.

[1] https://github.com/kerneis/cpc

[2] https://pdos.csail.mit.edu/archive/xoc/

Re: Generic Containers in C: Vec

#49
post #41

Earlier quoted context omitted.

There are less annoying ways to implement this in C. There are at least two different common approaches which avoid having macro code for the generic functions: The first is to put this into an include file #define type_argument int #include Then inside vector.h the code looks like regular C code, except where you insert the argument. foo_ ## type_argument ( ... ) The other is to write generic code using void pointer…

Those techniques being less annoying is highly debatable ;). Working with void* is annoying, header includes look quite ugly with the ## concatenation everywhere or even a wrapper macro. It also gets much worse when you need to customize the suffix (because type_argument is char* or whatever). Sometimes the best option is an external script to instantiate a template file.

I don't think

    T ## _foo (T foo, ...)
is that much different from

    ::foo (T foo, ...)
Same for:

    foo (Object * a)
vs:

    foo (void * a)

Re: Generic Containers in C: Vec

#50
post #22

Many C programmers need proper generic programming mechanisms (perhaps something like Zig's comptime) in C, but macros are the worst possible approach, and they don't want to switch to a different language like C++. As a result, they struggle with these issues. This is what I think the standardization committee should focus on, but instead, they introduced _Generic.

The biggest issue is the ABI for C - it's the lingua-franca of language interoperability and can't really be changed - so whatever approach is taken it needs to be fully compatible with the existing ABI. `_Generic` is certainly flawed but doesn't cause any breaking ABI changes.

That's also a major reason why you'd use C rather than C++. The C++ ABI is terrible for language interoperability. It's common for C++ libraries to wrap their API in C so that it can be used from other language's FFIs.

Aside from that another reason we prefer C to C++ is because we don't want vtables. I think there's room for a `C+` language, by which I mean C+templates and not C+classes - perhaps with an ABI which is a subset of the C++ ABI but superset of the C ABI.

Post reply on HN