[C++]配列の参照、その書き方

C++ではおよそ全ての型に対して、その実体へのエイリアス(別名)として参照を宣言できます。少しだけ安全なポインタとして扱うことが出来、コンパイル後はポインタと同じコードになります(ならない処理系もあるかもしれません)。そして、配列もまたその参照を書くことが出来ます。

変数宣言

int array[]{ 1,2,3,4,5 };

//配列の参照宣言
int(&r2)[5] = array;

//decltype(r1) = int(&)[5]
auto& r1 = array;

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

この様に、配列の参照を変数として書くときは
T(&name)[N]のように書き、Tに型名、nameに変数名、Nに要素数、となります。
ただし、配列の型Tと要素数Nは参照先の配列と一致している必要があります(Nはコンパイル時に確定する必要があります)。
しかし、これは書き辛いし見辛いので、auto&を使ってコンパイラさんに推論してもらうのをお勧めします。

引数宣言

//int型の5要素の配列を受け取る
void f(int(&array)[5]) {
    for(auto n : array) {
        std::cout << n << std::endl;
    }
}

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

関数の引数として配列を取るときは、変数宣言時と同じ書き方で書いてあげます。見辛いのでテンプレート使いたくなりますが、その場合はポインタに推論されるため、配列への参照にはなりません(ほぼ同じ意味にはなりますが)。

配列への参照を返す関数

一体いつこれを使うことになるのかは定かではありませんが、もちろん書くことが出来ます。しかし、奇妙さが倍増しになります・・・

//int型5要素の配列への参照を返すretarray関数
int (&retarray())[5] {
    static int array[]{ 1,2,3,4,5 };
    return array;
}

//cv、参照修飾、noexcept指定などは引数宣言の閉じかっこ後
int (&retarray2(int n) noexcept)[5] {
    static int array[]{ n,n+1,n+2,n+3,n+4 };
    return array;
}

//auto&を使って簡便に
auto& retarray3() {
    static int array[]{ 1,2,3,4,5 };
    return array;
}

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

この様に、配列の参照を返す関数を書くときは
T(&name(args...) const noexcept...)[N] {...}のように書き、Tに型名、nameに関数名、args...に引数列、Nに要素数、となり、変数宣言の時と同じように、TとNは参照先のものと一致していなければなりません。
CV、参照修飾や例外指定は普通の関数と同じ位置、つまりは引数宣言の閉じかっこの後に置きます。
こちらもauto&を使えるのでこちらで書くと少し見やすくなります(が、型が分かり辛くなります)。

しかし、戻り値型の後置き構文を使う事でかなり見やすく書くことが出来ます。

auto retarray() -> int(&)[5] {
    static int array[]{ 1,2,3,4,5 };
    return array;
}

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

後置きの書式は変数宣言時と同じ書き方です(名前はいりません)。これが一番見やすいのでこの書き方で書くといいでしょう。
関係ない注意点ですが、関数ローカルの配列への参照を返してしまわないように気を付けてください。

配列の参照のメリット

一見すると構文の奇妙さが目立ち、使いどころも分からない配列への参照ですが、その要素数コンパイル時に確定できるというメリットがあります。
どういう事でしょうか?

void func(int array[5]);
//decltype(func) = void (int *)

int array[2]{};
func(array);

関数の引数に配列を取るときはこのように書き、呼び出し側では配列の名前を渡します。引数宣言では要素数を書かなくてもいいのにあえて書くことで配列の要素数に制限を加えている気分になります。
なりますが、実際はこれは単なるポインタ渡しになっており、配列でなくてもint*となる物なら何でも渡すことが出来てしまいます。
[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

しかし、配列の参照を受け取るようにすると

void func(int(&array)[5]);
//decltype(func) = void (int (&)[5])

int array[2]{};
func(array);   //コンパイルエラー

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ
引数の型名に要素数がしっかりと入り、その要素数以外の配列を渡そうとするとコンパイルエラーとなります。もちろん、int*を渡すこともできません。しかも、関数の中でその要素数を超えてアクセスしている場合に警告を出してくれます(clangの場合)。
ある意味Conceptの様な制約を加えることが出来ているわけです。しかも、参照は実質ポインタなので実行時にはポインタ渡しと同じコードになっているはずです。

また、これを利用して静的配列の要素数を得るテクニックがあります。

template<typename T, size_t N>
constexpr auto size(const T(&)[N]) noexcept -> size_t {
   return N;
}

int array[5] = {5, 4, 3, 2, 1};
auto N = size(array); // N = 5

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ
C言語由来のマクロで書く方法もありますが、C++ならばこちらを使うべきでしょう。ちなみにC++17でこれはstd::size関数として標準ライブラリ入りしています(iteratorヘッダ)。

多次元配列の場合

配列は別に1次元だけでなく、多次元配列を宣言できます。そして多次元配列の参照も当然あります。さらに複雑な構文を要求されることはなく、普通に次元を書き足してやるだけです。

//int型3行4列の配列への参照を返す、retarray関数
int(&retarray() noexcept)[3][4] {
    static int array[3][4] = {{4, 3, 2, 1}, {8, 7, 6, 5}, {12, 11, 10, 9}};
    return array;
}

//int型3行4列の配列への参照を宣言
int(&array2)[3][4] = retarray();

//二次元配列の要素数を取得するsize関数
template<typename T, size_t N, size_t M>
constexpr auto size(const T(&)[N][M]) noexcept -> std::tuple<size_t, size_t> {
    return {N, M};
}

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ