En c++ un arreglo siempre se pasa por referencia en las funciones.
ya que un arreglo de int para el lenguaje es int * a, no importa si tu escribes el parámetro como int a[], int a[10] o int * a, porque a la hora de compilar todos son int *a. Es la única excepción donde pasa esto. ya que las demás variables si puedes elegir entre pasar por valor (se copia el valor a una variable temporal solo para el scope de la función) o por referencia, dirección de memoria.
Si haces la prueba de poner como parametro int a[]; y al llamar la funcion le pones un int, te va a saltar un error de que se esperaba un int *.
ya que un arreglo de int para el lenguaje es int * a, no importa si tu escribes el parámetro como int a[], int a[10] o int * a, porque a la hora de compilar todos son int *a. Es la única excepción donde pasa esto. ya que las demás variables si puedes elegir entre pasar por valor (se copia el valor a una variable temporal solo para el scope de la función) o por referencia, dirección de memoria.
Si haces la prueba de poner como parametro int a[]; y al llamar la funcion le pones un int, te va a saltar un error de que se esperaba un int *.
Por eso siempre hay que tener cuidado de manipular array en funciones aux.
Luego, pasar por referencia sirve cuando no podes o es más eficiente no tener un retorno.
Tenemos el ejemplo de scanf, nosotros pasamos las variables como referencia porque tu podrías leer más de una variable al mismo tiempo.
scarf("%d %d", &a, &b);
sino tendríamos que hacer
a = scarf("%d"); // ESTO ES INCORRECTO
sería más ineficiente porque tenes que llamarlo más veces para guardar más de una variable.
En esta respuesta de Stackoverflow hay una animación muy buena para entenderlo un poco mejor.