配列のサイズを取得するためのこのテンプレートコードはどのように機能しますか?


61

なぜこの種のコードはテスト配列のサイズを取得できるのでしょうか。テンプレートの文法に慣れていません。多分誰かが下のコードの意味を説明できるでしょうtemplate<typename,size_t>。また、参照リンクも推奨されます。

#define dimof(array) (sizeof(DimofSizeHelper(array)))
template <typename T, size_t N>
char(&DimofSizeHelper(T(&array)[N]))[N];

void InitDynCalls()
{
    char test[20];
    size_t n = dimof(test);
    printf("%d", n);
}

C ++ 11についてn3337のようなものを読みましたか?それはあなたの質問に関連するはずです!または...の使用を検討しましたかstd::arraystd::vector
Basile Starynkevitch

@BasileStarynkevitch読んだことがない。コードはサードパーティのライブラリに表示されます。意味を知りたいだけです。
シャドウフィーンド、

役立つ洞察については、norvig.com / 21-days.htmlも参照してください(そのページの作成者を確認してください)。
Basile Starynkevitch、


@BasileStarynkevitchそのリンクの関連性がわかりません。
オービットのライトネスレース

回答:


86

これは実際には説明するのが本当に難しいものですが、私はそれをやってみましょう...

まず、dimofあなたに伝え次元、または配列の要素数を。(「ディメンション」は、Windowsプログラミング環境で推奨される用語です)。

これは必要であるC++Cあなたに配列のサイズを決定するためのネイティブな方法を与えることはありません。


多くの場合、人々sizeof(myArray)はうまくいくと思っていますが、実際には要素の数ではなく、メモリ内のサイズがわかります。各要素はおそらく1バイト以上のメモリを使用します!

次に、彼らは試みるかもしれませんsizeof(myArray) / sizeof(myArray[0])。これにより、配列のメモリ内のサイズが、最初の要素のサイズで除算されます。これは問題なく、Cコードで広く使用されています。これの主な問題は、配列ではなくポインタを渡した場合に機能するように見えることです。メモリ内のポインタのサイズは、それが指すものが数千の要素の配列である場合でも、通常4バイトまたは8バイトです。


したがって、次に試すことC++は、テンプレートを使用して、配列に対してのみ機能するものを強制し、ポインタでコンパイラエラーを発生させることです。次のようになります。

template <typename T, std::size_t N>
std::size_t ArraySize(T (&inputArray)[N])
{
    return N;
}
//...
float x[7];
cout << ArraySize(x); // prints "7"

テンプレートは配列でのみ機能します。型(実際には必要ありませんが、テンプレートを機能させるために存在する必要があります)と配列のサイズを推定し、サイズを返します。テンプレートの記述方法では、ポインターを使用できない可能性があります。

通常、ここで停止できます。これは、C ++標準ライブラリにありstd::sizeます。


警告:以下で、それは毛深い言語弁護士の領域に入ります。


これはかなりクールですが、あいまいなエッジケースでも失敗します。

struct Placeholder {
    static float x[8];
};

template <typename T, int N>
int ArraySize (T (&)[N])
{
    return N;
}

int main()
{
    return ArraySize(Placeholder::x);
}

配列x宣言されていますが、定義されていないことに注意してください。関数(つまりArraySize)を呼び出すには、を定義するx必要があります

In function `main':
SO.cpp:(.text+0x5): undefined reference to `Placeholder::x'
collect2: error: ld returned 1 exit status

これはリンクできません。


問題のコードはその回避策です。実際に関数を呼び出す代わりに、正確なサイズのオブジェクト返す関数を宣言します。次に、そのsizeofトリックを使用します。

関数を呼び出すように見えますが、これsizeofは純粋にコンパイル時の構造であるため、関数が実際に呼び出されることはありません。

template <typename T, size_t N>
char(&DimofSizeHelper(T(&array)[N]))[N];
^^^^ ^                               ^^^
// a function that returns a reference to array of N chars - the size of this array in memory will be exactly N bytes

関数から配列を実際に返すことはできませんが、配列への参照を返すことはできます。

次にDimofSizeHelper(myArray)であるそのタイプにアレイであるN charSは。式は実際に実行可能である必要はありませんが、コンパイル時に意味があります。

したがってsizeof(DimofSizeHelper(myArray))、実際に関数を呼び出した場合に得られるであろうコンパイル時サイズがわかります。実際にそれを呼び出すわけではありませんが。

オースティンパワーズクロスアイド


最後のブロックが意味を成さなくても心配しないでください。奇妙なエッジケースを回避するのは奇妙なトリックです。このため、この種のコードを自分で記述せず、ライブラリの実装者にこの種のナンセンスを心配させます。


3
@Shadowfiendそれも間違っています。それは実際には関数の宣言ではないので、それはさらに醜いです。それは関数参照の宣言です...私はまだそれを説明する方法を理解しています。
BoBTFish

5
なぜそれが関数参照の宣言なのですか?「DimofSizeHelper」の前の「&」は、戻り値の型がchar(&)[N]であることを意味し、bolovの回答に従います。
シャドウフィーンド

3
@Shadowfiend絶対に正しい。私は頭を結び目に縛られたので、私はゴミを話していました。
BoBTFish '16年

次元は配列の要素数ではありません。つまり、1、2、3、またはそれ以上の次元の配列があり、それぞれが同じ数の要素を持つことができます。たとえば、array1D [1000]、array 2D [10] [100]、array3D [10] [10] [10]。それぞれ1000個の要素があります。
jamesqf

1
@jamesqf C ++のような言語では、多次元配列は他の配列を含む単純な配列です。コンパイラーの観点から見ると、1次配列の要素の数は、その内容(2次配列または3次配列の場合があります)とはまったく無関係であることがよくあります。
Phlarx

27
template <typename T, size_t N>
char(&DimofSizeHelper(T(&array)[N]))[N];

// see it like this:
//                char(&DimofSizeHelper(T(&array)[N]))[N];
// template name:       DimofSizeHelper
// param name:                             array
// param type:                          T(&     )[N])
// return type:   char(&                             )[N];

DimofSizeHelperT(&)[N]パラメータを取るテンプレート関数です。別名Tは、タイプのN要素のC配列char (&)[N]への参照であり、別名はN文字の配列への参照です。C ++では、charは変装したバイトであり、標準であるsizeof(char)ことが保証さ1れています。

size_t n = dimof(test);
// macro expansion:
size_t n = sizeof(DimofSizeHelper(array));

n戻り値の型のサイズが割り当てられDimofSizeHelperている、sizeof(char[N])されるがN


これは少し複雑です そして不必要。それを行う通常の方法は:

template <class T, size_t N>
/*constexpr*/ size_t sizeof_array(T (&)[N]) { return N; }

C ++ 17以降ではこれも不要です。std::sizeこれは、これを行う方法と同じですが、より一般的な方法で、任意のstlスタイルのコンテナーのサイズを取得できます。


BoBTFishによって指摘されたように、エッジケースには必要です。


2
サイズを取得したい配列をODRで使用できない場合は必要です(宣言されていますが、定義されていません)。確かに、かなりあいまいです。
BoBTFish

テンプレート関数で型を説明していただきありがとうございます。それは本当に役立ちます。
シャドウフィーンド、

3
私たちは持っているstd::extentコンパイル時であるC ++ 11以降。
LF
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.