Also, generic functions in Common Lisp don't suffer from nearly the same amount of complexity as can be found in C++: there is always only one signature (lambda-list) for any function name, whether generic or not, there are no implicit conversions, no memory-allocation details, value/pointer/reference distinction, constness, virtualness, overloading of assignment operators etc.
I was under the impression that the function signature was not the issue raised... but the fact that the same function name could lead to completely different behaviors depending on a context. In the case of generic functions, the specific function implementation is usually (ignoring some of the other matching features) determined by the input types. The input types define the context.
All that said, I realize now that this might be less of a problem in CL if you just group your defmethods in the same area. If the problem is grepping for the definition, it is easy enough to rearrange things so that the related functions are in a close-enough place. In C++, the classes try to own their methods -- with the exception of warily-regarded "friend" methods.
http://www.cs.cmu.edu/Groups/AI/html/hyperspec/HyperSpec/Bod...
Though grep would probably do just fine too, since method definitions are defined with 'defmethod' instead of 'defun', so you can see at a glance whether something is generic or not.
Package is essentially a collection that maps symbols to values. In CL packages have (at least) two namespaces: one for functions, other for any kind of variables. The reason for this is that when you write the form
(fun #'a b)
you and the compiler can be sure that 'fun' and 'a' are both meant to be functions. There are both advantages and disadvantages to this.
Of course you can not look at the function and immediately know all consequences. The real point is that there is a procedure you can follow which will let you determine the answer.
1. Locate the "write" function. There will only be one, because C has no namespaces.
2. Read the "write" function.
3. Repeat recursively as needed (including for macros).
For C++, the equivalent would be something like fd->write(buf), and the procedure is:
1. Determine the type of 'fd'.
2. Determine what subtypes you could have there and which you might actually have in hand. Or is the class not virtually inherited in which case it doesn't matter?
3. Determine what the write method does. In order to do so, have intimate knowledge of all operator overloading any value used in the write method may have.
4. Figure out what "buf" is and whether it magically overloads other operators.
write(fd, buf, size) resolves to one function with three arguments that themselves can't be that magical, and usually one basic approach to the question of memory management. fd->write(buf) involves classes, inheritance, potentially overloads, interfaces that 'buf' may correspond to and the potential need to follow a chain of some number of functions just to see whether that was constructed automatically into another type, endless permutations of how memory may be handled, and so on and so forth. The assembler that the C code will generate will basically push three arguments and call a function; the assembler the C++ generates is effectively unbounded in complexity. This assembler complexity directly corresponds to complexity that must be understood in order to understand the line of code.
In general I'd prefer the C++, but when writing a kernel where every twitchy detail counts for everything and the slightest bit of "wrong" could be a rootable security bug, I see the counterarguments.