Как работает этот шаблон кода для получения размера массива? - PullRequest
61 голосов
/ 16 октября 2019

Интересно, почему этот вид кода может получить размер тестового массива? Я не знаком с грамматикой в ​​шаблоне. Может быть, кто-то может объяснить значение кода под 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);
}

Ответы [ 2 ]

86 голосов
/ 16 октября 2019

Это действительно сложно объяснить, но я попробую ...

Во-первых, dimof сообщает вам измерение , или количество элементовв массиве. (Я считаю, что «измерение» является предпочтительной терминологией в средах программирования Windows.)

Это необходимо, потому что C++ и C не дают вам собственного способа определения размера массива.


Часто люди предполагают, что sizeof(myArray) будет работать, но это фактически даст вам размер в памяти, а не количество элементов. Каждый элемент, вероятно, занимает более 1 байта памяти!

Далее они могут попробовать sizeof(myArray) / sizeof(myArray[0]). Это дало бы размер в памяти массива, деленный на размер первого элемента. Это нормально и широко используется в C коде. Основная проблема заключается в том, что он будет работать, если вы передадите указатель вместо массива. Размер указателя в памяти обычно составляет 4 или 8 байт, даже если он указывает на массив из 1000 элементов.


Итак, следующая попытка в 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 ++ Standard Libary как 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 char с. Выражение на самом деле не обязательно должно выполняться, но оно имеет смысл во время компиляции.

Поэтому sizeof(DimofSizeHelper(myArray)) сообщит вам размер во время компиляции того, что вы получите, если выдействительно вызвал функцию. Хотя мы на самом деле не называем это.

Austin Powers Cross-Eyed


Не беспокойтесь, если последний блок не имел никакого смысла. Это странный трюк, чтобы обойти причудливый крайний случай. Вот почему вы не пишете такого рода код самостоятельно и позволяете разработчикам библиотек беспокоиться о такой ерунде.

27 голосов
/ 16 октября 2019
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];

DimofSizeHelper - это функция шаблона, которая принимает параметр T(&)[N] - то есть ссылку на массив C из N элементов типа T и возвращает char (&)[N], то есть ссылку на массивN символов. В C ++ символ является замаскированным байтом, и по стандарту 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, это необходимо для пограничного случая.

...