Live data from Hacker News

Function overloading in C

gist.github.com

41–50 of 57 posts

Re: Function overloading in C

#41
post #25

#define add(a, b) _Generic(a, int: addi, char*: adds)(a, b) Just because you can doesn't mean you should. Someday, someone will have to read that code. Note that when "add" returns a string, it's done an allocation, but for the integer case, it hasn't called "malloc". Now you'll now need to know the type to get rid of it. Back in C99, generic trig functions were put in to make the number-crunching people happy. After…

> Just because you can doesn't mean you should.

The fact that it's new doesn't mean it's in bad taste.

GCC has had a similar mechanism in C for decades. It was experience with that mechanism, and similar extensions in other compilers, that led to it being in the C Standard.

Re: Function overloading in C

#42
post #12

This whole _Generic stuff is a mistake. There is already a descendant dialect of C which has nice overloading, namely C++. Analogy: imagine some people aren't happy with the new Fortran. Fortran 66 is the best language ever, and the only flavor of Fortran anyone should use ... except: they like a couple of features from Fortran 2008 and Fortran 90. So, they whip out the Fortran 66 standard and fork it to create "Fort…

But the OP title is misleading, this isn't function overloading at all. It's macro overloading. Subtle but important difference. For example, you can't pass the OP's "add" around as a function pointer. You can't declare its prototype in an outside file (leaving the linker to link it in) without putting its full definition in said outside file. Etc. It also gives, IMO, better error messages than C++. If we take OP's c…

> For example, you can't pass the OP's "add" around as a function pointer.

To be fair, in C++ you have to say which overload you want to point a function pointer at. So the fact that you have to specify the real function you want to point at, in C, doesn't seem unreasonable.

Re: Function overloading in C

#43

Earlier quoted context omitted.

It's hard to say what's more powerful. Certainly templates involve more computation, but _Generic can do some things templates can't (for good and for ill) and vice-versa.

Well, since templates are turing complete, I would tend to say that they are more powerful. But they work very differently. Templates do code generation and _Generic is just a fancy-pants type-based macro-dispatch. Like I said, I do not think _Generic is a bad thing (though it can get really messy—which is not to say that Templates can't), just that the example given in the OP is a bit contrived.

It is computationally more powerful, sure. If that's what you'd intended to be talking about, then fine. But that is a distinct notion from "you are able to do more with it" - which is typically the more relevant bit when discussing the power of a feature in a programming language outside a restricted academic context.

(Incidentally, other things being equal - including ability to get done the thing you want to get done - "Turing complete" is a bug, not a feature!)

Expanding a little - "Turing complete" means that your system is capable of computing anything that can be computed in some encoding, but that doesn't mean that it is linked to the rest of the world in a way that will do something useful with that encoding of the result.

Token-manipulating C macros, while obviously of less powerful in the sense of computability, can do things that templates cannot do (name mangling, annotations, compiler pragmas) - and with _Generic, any of these can now be type directed. Of course, with great power comes the responsibility never to use half of that... but that's pretty true of C++ as well :p

Re: Function overloading in C

#44

Earlier quoted context omitted.

I find the lack of implicit conversion from void * really annoying. Any time I see someone cast the return type of malloc in a .c file, I curse C++ and die a little. This sounds like a trivial thing but it very much changes the feel of the language, namely into one where you need to insert pointless casts to satisfy a whiney compiler.

Why the hate over casting malloc? It really is not a huge issue. A related question is why do people use malloc if calloc is available?

Probably the best reason to hate it is if you didn't include the right headers and malloc is therefore implicitly defined to return int. That creates a bug on most 64-bit compilers where int is 32 bits. The cast will mask the bug.

But more stylistically, let's be honest, something like this is way too verbose:

    struct foo *bar = (struct foo*)malloc(sizeof(struct foo));
I see code like this often, and I think the only explanation I can think of is that people don't know the language well enough to write:

    struct foo *bar = malloc(sizeof(*bar));
> A related question is why do people use malloc if calloc is available?

Seems like a very different question... calloc multiplies, calls malloc, and zeros. I guess if you don't want to multiply, don't want to zero, don't want to bother specifying another parameter, some combination of these, then malloc might be a better choice. Though I do recall a few years back the OpenBSD folks (possibly others too?) focusing on making sure calloc checks for overflow when it multiplies. [See also: reallocarray]

Re: Function overloading in C

#45

Earlier quoted context omitted.

Why the hate over casting malloc? It really is not a huge issue. A related question is why do people use malloc if calloc is available?

Probably the best reason to hate it is if you didn't include the right headers and malloc is therefore implicitly defined to return int. That creates a bug on most 64-bit compilers where int is 32 bits. The cast will mask the bug. But more stylistically, let's be honest, something like this is way too verbose: struct foo *bar = (struct foo*)malloc(sizeof(struct foo)); I see code like this often, and I think the only…

If you turn on wall you should be warned that you didn’t include the right headers?

I was under the impression that lots of implementations of malloc actually call calloc under the hood. Unless you are calling malloc in a tight loop I can’t see the extra step of zeroing being a issue. In one of my programs where performance is critical I changed over all my mallocs to calloc and couldn’t measure the time change - I did find I had fewer difficult-to-duplicate bugs though.

Edit. Interestingly CERT recommends casting [1]. Seems to solve a much worse issue than forgetting to include stdlib.h

1. https://www.securecoding.cert.org/confluence/display/c/MEM02...

Re: Function overloading in C

#46

Earlier quoted context omitted.

With a couple of minor exceptions, C++ is a superset of C.

It's not just a couple. There are a large variety of things in C that Cxx does not support at all (e.g., VLAs and _Generic), and many more things that, though they are in both languages behave very differently (e.g., sizeof). It's fine for people to like Cxx, but I do wish we could stop perpetuating the falsehood that C is just a subset of Cxx. Cxx, when it started was a pure superset of C, but that is no longer the…

Alright, I'll rephrase. There are a zillion differences, but with a couple of minor exceptions, there is very little C++-incompatible C that cannot be straightforwardly expressed as C++-compatible C. I mean, gcc -Wall even turns on -Wc++11-compat and -Wc++14-compat now.

Also, sizeof doesn't behave differently, the arguments that get passed to sizeof are different in a few cases, e.g. sizeof('x'). See Annex C:

http://www.lcdf.org/c%2B%2B/clauseC.html

Re: Function overloading in C

#47

Earlier quoted context omitted.

I find the lack of implicit conversion from void * really annoying. Any time I see someone cast the return type of malloc in a .c file, I curse C++ and die a little. This sounds like a trivial thing but it very much changes the feel of the language, namely into one where you need to insert pointless casts to satisfy a whiney compiler.

Why the hate over casting malloc? It really is not a huge issue. A related question is why do people use malloc if calloc is available?

Why programmers don't always use calloc: good question.

Firstly, anyone who does mallocs a block, and then immediately does a memset(block, 0, size) on that block almost certainly should be using calloc. The Linux kernel has kzalloc for this purpose as an alternative to kmalloc and it's fairly widely used.

Sometimes it is inefficient to set something to zero, when that is not the true initial value (some or all of the zeros will be overwritten with the true initial value before the object is used). If you malloc some structure and then painstakingly initialize every member, then calloc is wasteful.

If you malloc some structure, and then forget to initialize some of the members, calloc will hide that bug by initializing them to all zero bits. This might not be the correct value. If the uninitialized parts of the structure aren't clobbered from zeros, you can get an accurate diagnostic about that from a tool like Valgrind. On the other hand, if you aren't using that tool, the behavior with zeros is more deterministic. E.g. if a pointer is not initialized, but overwritten with zeros, on most mainstream platforms that will be a null pointer whose dereferencing uses are detected. If you don't have such a tool available, having the uninitialized bits be all zero is arguably better.

Much of this reasoning mirrors reasoning about whether to initialize a local variable to zero, if its true initialization actually happens later.

Re: Function overloading in C

#48

Earlier quoted context omitted.

With a couple of minor exceptions, C++ is a superset of C.

It's not just a couple. There are a large variety of things in C that Cxx does not support at all (e.g., VLAs and _Generic), and many more things that, though they are in both languages behave very differently (e.g., sizeof). It's fine for people to like Cxx, but I do wish we could stop perpetuating the falsehood that C is just a subset of Cxx. Cxx, when it started was a pure superset of C, but that is no longer the…

[deleted]

Re: Function overloading in C

#49
post #12

Earlier quoted context omitted.

But the OP title is misleading, this isn't function overloading at all. It's macro overloading. Subtle but important difference. For example, you can't pass the OP's "add" around as a function pointer. You can't declare its prototype in an outside file (leaving the linker to link it in) without putting its full definition in said outside file. Etc. It also gives, IMO, better error messages than C++. If we take OP's c…

> For example, you can't pass the OP's "add" around as a function pointer. To be fair, in C++ you have to say which overload you want to point a function pointer at. So the fact that you have to specify the real function you want to point at, in C, doesn't seem unreasonable.

No, you don't have to say that in C++ and there isn't any way to, in fact.

   // Well, you do say it ... with type!
   int (*ptr)(int, int) = add;
This will take the address of add(int, int) and not some other add like add(double, double). There is no explicit way to refer to one or the other.

You're right in that we can't point to the collection of add functions as such. There is no generic function add of which the overloads are its implementations, such that we can have a pointer to this generic add aggregate and have ptr(a, b) be subject to overload resolution.

I think that if you want to be able to construct an object ptr out of a set of plain functions, such that ptr(a, b) chooses among those functions based on arguments, that may be doable. Just not with a plain function pointer.

Re: Function overloading in C

#50

Earlier quoted context omitted.

C++ incorporates nearly all of C99 directly, save for VLAs, restrict, designated initializers, and a lot of small stuff that won't affect any modern-ish C code (e.g., no implicit int). VLAs and restrict are explicitly not in C++ because they're somewhat broken (C++14 did add "dynamic arrays" which are VLA-lite, and there's work on a better restrict-like functionality, cf. https://isocpp.org/files/papers/N3988.pdf ),…

I find the lack of implicit conversion from void * really annoying. Any time I see someone cast the return type of malloc in a .c file, I curse C++ and die a little. This sounds like a trivial thing but it very much changes the feel of the language, namely into one where you need to insert pointless casts to satisfy a whiney compiler.

>it very much changes the feel of the language, namely into one where you need to insert pointless casts to satisfy a whiney compiler

You could use the exact same argument against any type-safety feature: inserting "pointless" forward declarations, inserting "pointless" const qualifiers, etc., to "satisfy a whiney compiler."

The thing is, implicit conversion from void* is plainly unsound, type theoretically speaking. It has certain advantages, but is not necessary in order to have those advantages. For example, it makes use of malloc easier, but one could also make the vast majority of uses of malloc easier with something like

    #define ALLOC(TYPE,N) ((TYPE*)malloc(N*sizeof(TYPE)))
Or at least, one could if C had a remotely regular type syntax and/or actual type genericity. In contrast, you actually can write

    template 
    T* alloc(std::size_t n) {
        return static_cast  (malloc (n * sizeof (T)));
    }
in C++, and it's even straightforward to add additional logic like overflow detection. Mark it inline and stick it in a header, and it's effectively the same as the above ALLOC macro, except that it actually works. Now, contrast

    int* array = malloc(256*sizeof(int));
with

    int* array = alloc (256);
The C++ expression is simpler. Now, if you change the type of the array, but forget to change the allocation expression (for whatever reason; maybe you're using the absurd convention of declaring all your variables before you start initializing and using them):

    char* array = malloc(256*sizeof(int));
    // vs
    char* array = alloc (256);
You get a type error with the C++ version, and the C version silently compiles. Not too much of an issue in this case -- you'll just waste a bunch of memory -- but go from char to int instead of int to char, and now you've got buffer overflows.

As for the argument made elsewhere in this thread that explicitly casting the result of malloc in C can mask the fact that malloc has been implicitly delcared, that's a language bug -- one that was fixed more than fifteen years ago.

Post reply on HN