"Pointers make my head spin" etc ...

Robin2:
In that case it makes even less sense for the compiler not to also cast an ordinary variable name to a pointer when it is "used in an expression that accepts a pointer as an operand"

It is just a rule decided for that type and is explained in the section "standard conversions". I'll try my best to give it some sense though:

4.2 Array-to-pointer conversion [conv.array]
1 An lvalue or rvalue of type “array of N T” or “array of unknown bound of T” can be converted to a prvalue of type “pointer to T”. The result is a pointer to the first element of the array.

An array is unrelated to 'ordinary' variables, and it has its own features just as primitive types have their own standard "integral conversions".

Similarly though, you can get a pointer to an array by using the address of operator (&). This gives you an actual "pointer to array", whereas the standard conversion only gives you a pointer to the first element (you can also create a reference to an array).

To make things a bit clearer, the standard conversion for arrays is needed to access array elements. This is because the subscripting operator works with pointers (not actually an array specific operator). It has a clause in its description mentioning this:

The expression E1[E2] is identical (by definition) to *((E1)+(E2))

Its identical form (second one listed above) shows that either E1 or E2 must be a pointer as it is dereferenced to access the value. You can also do a proof of this.

The code below is accessing the second element of the array two different ways using the subscripting operator, showing that at least one operand must be a pointer:

  int array[5];

  int a = array[1];

  int b = 1[array];

So even though an implicit array to pointer conversion may seem strange, it is in fact needed to access elements of arrays.

You might be wondering what use is an array that isn't converted to a pointer (which is why many consider arrays to be simply pointers). Well it is because arrays provide more information than a pointer. Size/length info is lost once you have a pointer. You can maintain this with an array reference or array pointer:

I used a typedef to make the syntax a bit easier on the eye.

typedef int ArrayType[5];

void setup() {

  ArrayType array = {1,2,3,4,5};

  AddOneToArray(array);
}

void AddOneToArray( ArrayType &array ){

  for( auto &element : array ){  //Size info is maintained
    element += 1;
  }
}

void loop() {}

You could use an ArrayType* as the function parameter, you'd just need to dereference the pointer to return to the actual array.

This next example is a little more complex and shows two things, returning an array (reference) from a function, and also an unrelated example showing a real array pointer.

typedef int ArrayType[5];

class MyArray{
  public:
  
    //Allows conversion of class type to internal array type (returns a reference to array)
    operator ArrayType&(){
      return data;
    }

  private:
    ArrayType data;
};

void setup() {

  MyArray foo;

  //Use conversion operator provided by class, then access an array element:
  foo[4] = 1;


  ArrayType bar = {1,2,3,4,5};
  //Take pointer to array
  ArrayType *ptr = &bar;

  //Do something with array pointer
  (*ptr)[4] = 2;

}

void loop() {}