Как работает этот шаблон кода для получения размера массива?

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);
}
Тень
источник
Вы читали что-то вроде n3337 о C ++ 11 ? Это должно соответствовать вашему вопросу! Вы считаете ли с помощью std::arrayили std::vector....
Basile Starynkevitch
@BasileStarynkevitch Я не читал это. Код появляется в сторонней библиотеке. Я просто хочу выяснить смысл.
Shadow
См. Также norvig.com/21-days.html для полезного понимания (и посмотрите, кто является автором этой страницы).
Старынкевич
2
Похоже, дубликат stackoverflow.com/questions/6106158/…
sharptooth
@BasileStarynkevitch Я не понимаю актуальность этой ссылки.
Гонки

Ответы:

86

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

Во-первых, 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 as 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))вам сообщат размер во время компиляции того, что вы получите, если бы вы действительно вызвали функцию. Хотя мы на самом деле не называем это.

Остин Пауэрс, косоглазый


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

BoBTFish
источник
3
@ Shadowfiend Это тоже неправильно. Все даже ужаснее, потому что на самом деле это не объявление функции, это объявление ссылки на функцию ... Я все еще выясняю, как это объяснить.
BoBTFish
5
Почему это объявление ссылки на функцию? «&» Перед «DimofSizeHelper» означает, что тип возвращаемого значения - char (&) [N], в соответствии с ответом Болова.
Shadow
3
@ Shadowfiend Совершенно верно. Я просто говорил ерунду, потому что у меня завязался мозг.
BoBTFish
Размер - это не количество элементов в массиве. То есть у вас может быть 1, 2, 3 или более массивные массивы, каждый из которых может иметь одинаковое количество элементов. Например, массив1D [1000], массив 2D [10] [100], массив3D [10] [10] [10]. каждый из которых имеет 1000 элементов.
jamesqf
1
@jamesqf В таких языках, как C ++, многомерный массив - это просто массив, содержащий другие массивы. С точки зрения компилятора, количество элементов в первичном массиве часто полностью не связано с его содержимым - это могут быть вторичные или третичные массивы.
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];

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

Болов
источник
2
Это необходимо, если вы не можете использовать ODR для массива, размер которого вы хотите принять (он объявлен, но не определен). По общему признанию, довольно неясный.
BoBTFish
Спасибо, что объяснили тип в функции шаблона. Это действительно помогает.
Shadow
3
Начиная std::extentс C ++ 11, время компиляции.
LF