プロが教える店舗&オフィスのセキュリティ対策術

とあるC++プログラミングの入門書で、どうしても理解出来ないコードがあります。
配列の要素数の取得法で、通常は「sizeof (array) / sizeof *(array)」
*arrayは配列変数
としていたのを、テンプレートを使った別の方法として以下のようなコード
-----------------------------------------------------------
#include <iostream>
using namespace std;

#define ARRAY_SIZE(array) (sizeof *ARRAY_SIZE_(&(array)))

template <typename T,size_t N>
char (*ARRAY_SIZE_(T(*)[N]))[N];

int main() {
int n[40];
cout << ARRAY_SIZE(n) << endl;
}
-----------------------------------------------------------
&(array)がT型N要素の配列へのポインタ、それがtemplateから引数となって "T(*)[N]" になっているところまでは何となく理解できます。
*ARRAY_SIZE_がそれを括弧で括っているのは関数の様に見えますが、関数としての実装がありません。
"*ARRAY_SIZE_()"は関数なのでしょうか?
またchar型配列のポインタの実体はどこにあるのでしょうか?
結果、char型 "(*ARRAY_SIZE_(T(*)[N]))" の "[N]" 要素数の配列として返しているようですが、その前に既にsize_tの "N" として答えが出ているので助長にも見えます。
勿論、このコードのコンパイルは通って正しい答え「40」が得られました。
C++初心者で頓珍漢な質問かもしれませんが、よろしくご教示お願いします。

質問者からの補足コメント

  • > またchar型配列のポインタの実体はどこにあるのでしょうか?

    質問が変かもしれません。
    どこかにchar型配列の実体が作られるのでしょうか?

      補足日時:2016/08/25 11:49

A 回答 (2件)

文法的には


char (*ARRAY_SIZE_(T(*)[N]))[N];
は ARRAY_SIZE_ という関数の宣言で
・引数は T(*)[N] つまり「T 型の要素を N個持つ配列」へのポインタ
・返り値は char (*)[N] で「char 型の要素を N個持つ配列」へのポインタ
です. C の文法を受け継いでいるので非常に読みにくくなっていますが, 「ARRAY_SIZE_」の直後に「(」があることから「ARRAY_SIZE_ が関数名である」とわかりますし, 対応する「)」までが引数リストになります. そして, 引数リストまでの部分, つまり「ARRAY_SIZE_(T(*)[N])」の部分を「X」と置きなおすと
char (*X)[N];
となることから「関数を呼び出すと char (*)[N] が返ってくる」ことがわかります.

これも C++11 以降だと auto を使って
template <typename T,size_t N>
auto ARRAY_SIZE_(T(*)[N]) -> char (*)[N];
と書くことができるので, ちょっとは見やすいかと.

マクロ ARRAY_SIZE では返り値を * で逆参照して「char 型の要素を N個持つ配列」をつくり, それに対して sizeof を適用することで配列の要素数である N を求めています.

でもこれ, ポインタの代わりに参照を使って
#define ARRAY_SIZE(array) (sizeof ARRAY_SIZE_(array))
template <typename T,size_t N>
char (&ARRAY_SIZE_(T(&)[N]))[N];
としても同じなんじゃないかなぁ.

あとついでですが, ARRAY_SIZE_ を関数にしているのは「テンプレート引数を自動的に推定する」ためです. 関数テンプレートでは (可能なら) テンプレート引数を関数の実引数から推定します. つまり, 挙がっている例では
ARRAY_SIZE(n)
から (紆余曲折を経て)
T = int, N = 40
となります. しかし, この自動推定はクラステンプレートには働かないので,
template <typename T, size_t N, T (&)[N]>
struct ArraySize {
enum { size = N };
};
としても意味がありません. 逆に言うと「自動推定するためには関数テンプレートでなければならない」ので, 例えば
make_pair
のような補助関数があったりします.
    • good
    • 1
この回答へのお礼

Tacosanさん

とても詳しい解説ありがとうございます。
ロベールの…という有名なC++入門本の中の「コラム」に出てきたコードで、それまで理解していたのに、いきなり難しいコードが出てきたので立ち往生していました。
Tacosanさんの解説を何度も読み返して、理解できるまで頑張ります。
「解決」とさせていただきます。
どうもありがとうございました。

お礼日時:2016/08/25 15:54

ARRAY_SIZE_ は関数ですけど, 今の場合その関数は sizeof のオペランドとしてしか使っていません. そして sizeof のオペランドは評価されないので, ARRAY_SIZE_ という関数そのものの実装は必要ありません (「char型配列の実体」も不必要). C++11 以降であれば constexpr を使って


template <typename T, size_t N>
constexpr size_t ARRAY_SIZE(T (&)[N]) { return N; }
とするところですが, C++98 (や C++03) に constexpr は存在しないので, 「コンパイル時定数にする」ために努力をしているものだと思われます.

あと, 「既にsize_tの "N" として答えが出ている」んだけど, その N はテンプレートの中にしか存在しないので, 外に持ち出すには関数の返り値であったり列挙型の要素として与えたりするなどの工夫が必要です.
    • good
    • 1
この回答へのお礼

Tacosanさん

回答ありがとうございます。
template内のsize_tの"N"を外へ持ち出す手段として、実装の無い関数"ARRAY_SIZE_(T(*)[N]))"を利用しているのですね。
理屈としては理解しました。
文法的なこともより理解できるようになりたいと思います。
どうもありがとうございました。

お礼日時:2016/08/25 13:03

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!