> there is no reason to not just enforce using tabs for indentation since they are objectively superior.

I just demonstrated one. The semantics of tabs is that they move the printing position to the next tab stop position, which is absolutely determined, rather than relatively.

This does not work for nested indentation in modern (or even not-so-modern) programming languages.

If we re-imagine the tab as simply being a macro which expands to a configurable number of spaces, then tabs could be objectively superior.

It makes sense to advocate for that, and I'm all for it; but I don't place a high probability on it happening. Though a simple idea, it would have to be in every editor, and other tools.

You almost want to re-purpose some other control character for this purpose and leave tab alone.

About:

void foo(arg, arg
arg, arg)
{
...
}

Note that editor support this, so the only keystrokes you need to enter this from scratch, for instance in Vim, are:

void foo(arg, arg[Enter]arg, arg)[Enter]{[Enter]...[Enter]}

The important attribute here is that the parameter list, which is an independent element of the phrase structure of the language, is presented in such a way that a bounding rectangle can be imagined around it. That rectangle includes only tokens from that element, and not from any other element:

+----------+
void foo|(arg, arg |
| arg, arg)|
+-----------+
{
...
}

Boxes help readability; that's why we use tables and grids for presenting information or laying out user interfaces.

Ideally, this principle would be applied to every syntactic element.

Looking at complex examples of Lisp code, I can't find a single counter-example: every sub-expression can be boxed in a rectangle which includes only that expression and its constituents: modulo a few closing parens that are written out to the right: )))). A big example of this is given below.

Now in the descendants of C syntax, the predominant brace placements throw a monkey wrench into this idea. We usually cannot draw a bounding box around a multi-line compound statement or other braced element such that the braces are included, and no tokens of neighboring constructs are included.

About that other style, for consistency, I would want this:

void foo(
arg, arg,
arg, arg
)
{
...
}

if the opening parenthesis is not on the same line as the arguments, the closing one should not be either.

I work in a shop where the style closely resembles this one, except the parameter declarations are on separate lines:

int put_bytes(
int fd, // comment
char *buffer, // comment
size_t size, // comment
)
{
}

Now I personally don't like this, but I can work with it, and can see the advantages. If you have to add, remove or reorder arguments you are not fiddling with the parentheses at all. When you take a unified diff of the edit, it does not include lines that only differ because of a parenthesis. The lines marked + or - are all "payload". The inner alignment of the parameter names is helpful, as is having the space for commenting the parameters.

I still wouldn't do this in a side project where I dictate the coding convention, but I can respect that and work with it without grumbling.

Now about that bounding box thing, here is a random example I picked from the SBCL compiler sources. The file is:

https://github.com/sbcl/sbcl/blob/master/src/compiler/srctra...

The macro bound-binop, with some ASCII rectangles inserted, looks like this.

Only the closing parentheses at the end violate the principle: the rectangle includes more parentheses than are strictly part of that syntax, and this problem is minimized by closing the parentheses together on the same line.

I suspect that the programmers who work with Lisp and find it readable all grok this bounding box model, whether consciouosly or not.

(defmacro bound-binop (op x y)
+-----------------------------------------------------------------------------------------------+
| (with-unique-names (xb yb res) |
| +-------------------------------------------------------------------------------------------+ |
| | `(and ,x ,y | |
| | +-----------------------------------------------------------------------------------+ | |
| | | (with-float-traps-masked (:underflow :overflow :inexact :divide-by-zero) | | |
| | | +-------------------------------------------------------------------------------+ | | |
| | | | +-------------------------------------+ | | | |
| | | | (let* |((,xb (type-bound-number ,x)) | | | | |
| | | | | (,yb (type-bound-number ,y)) | | | | |
| | | | | (,res (safely-binop ,op ,xb ,yb))) | | | | |
| | | | +-------------------------------------+ | | | |
| | | | +---------------------------------------------------------------------------+ | | | |
| | | | | (set-bound ,res | | | | |
| | | | | +--------------------------------------------------------------+ | | | | |
| | | | | | +----------------------------+ | | | | | |
| | | | | | (and |(or (consp ,x) (consp ,y)) | | | | | | |
| | | | | | +----------------------------+ | | | | | |
| | | | | | ;; Open bounds can very easily be messed up | | | | | |
| | | | | | ;; by FP rounding, so take care here. | | | | | |
| | | | | | +-----------------------------------------------------+ | | | | | |
| | | | | | |,(ecase op | | | | | | |
| | | | | | | +-------------------------------------------------+ | | | | | | |
| | | | | | | | (sb-xc:* | | | | | | | |
| | | | | | | | ;; Multiplying a greater-than-zero with | | | | | | | |
| | | | | | | | ;; less than one can round to zero. | | | | | | | |
| | | | | | | | `(or (not (fp-zero-p ,res)) | | | | | | | |
| | | | | | | | (cond ((and (consp ,x) (fp-zero-p ,xb)) | | | | | | | |
| | | | | | | | (>= (abs ,yb) 1)) | | | | | | | |
| | | | | | | | ((and (consp ,y) (fp-zero-p ,yb)) | | | | | | | |
| | | | | | | | (>= (abs ,xb) 1))))) | | | | | | | |
| | | | | | | +-------------------------------------------------+ | | | | | | |
| | | | | | | | | | | | | |
| | | | | | | +-------------------------------------------------+ | | | | | | |
| | | | | | | | (sb-xc:/ | | | | | | | |
| | | | | | | | ;; Dividing a greater-than-zero with | | | | | | | |
| | | | | | | | ;; greater than one can round to zero. | | | | | | | |
| | | | | | | | `(or (not (fp-zero-p ,res)) | | | | | | | |
| | | | | | | | (cond ((and (consp ,x) (fp-zero-p ,xb)) | | | | | | | |
| | | | | | | | (<= (abs ,yb) 1)) | | | | | | | |
| | | | | | | | ((and (consp ,y) (fp-zero-p ,yb)) | | | | | | | |
| | | | | | | | (<= (abs ,xb) 1))))) | | | | | | | |
| | | | | | | +-------------------------------------------------+ | | | | | | |
| | | | | | | | | | | | | |
| | | | | | | +-------------------------------------------------+ | | | | | | |
| | | | | | | | ((sb-xc:+ sb-xc:-) | | | | | | | |
| | | | | | | | ;; Adding or subtracting greater-than-zero | | | | | | | |
| | | | | | | | ;; can end up with identity. | | | | | | | |
| | | | | | | | `(and (not (fp-zero-p ,xb)) | | | | | | | |
| | | | | | | | (not (fp-zero-p ,yb)))))))))))) | | | | | | | |
| | | | | | | +-------------------------------------------------+ | | | | | | |
| | | | | | +-----------------------------------------------------+ | | | | | |
| | | | | +--------------------------------------------------------------+ | | | | |
| | | | +---------------------------------------------------------------------------+ | | | |
| | | +-------------------------------------------------------------------------------+ | | |
| | +-----------------------------------------------------------------------------------+ | |
| +-------------------------------------------------------------------------------------------+ |
+-----------------------------------------------------------------------------------------------+