Мы разберем это свободно, я использую INCITS + ISO + IEC + 14882-2003. Я процитирую мелкие вещи, но некоторые более сложные вещи слишком велики, чтобы их цитировать.
sizeof
определено в §5.3.3 и гласит (сокращенно):
Оператор sizeof возвращает количество байтов в объектном представлении своего операнда. Операндом является либо выражение, которое не вычисляется, либо идентификатор типа в скобках.
Другими словами, он возвращает размер типа в байтах или находит тип выражения и определяет его размер. У нас нет типа, у нас есть выражение arrayCountofHelper(arr)
.
Вы можете разобрать это выражение, взглянув на определения primary-expression
и postfix-expression
, как они определены в §5.1 и §5.2 соответственно. Вы найдете, что это postfix-expression
и соответствует требованиям вызова функции (§5.2.2).
Теперь вернемся к sizeof
. Мы заботимся только о типе этого выражения вызова функции (чтобы мы могли определить его размер), а §5.2.2 / 3 говорит:
Тип выражения вызова функции - это тип возврата статически выбранной функции [...]. Этот тип должен быть полным типом объекта, ссылочным типом или типом void
.
Таким образом, нам нужно найти тип возвращаемого значения функции, которая будет вызываться (помните, это все не оценено) с помощью arrayCountofHelper(arr)
. arrayCountofHelper
- это шаблон функции, который мы будем создавать (§14.7), поэтому нам нужно сделать это до того, как мы создадим экземпляр функции для получения возвращаемого типа.
Все параметры шаблона должны иметь значения (§14.8.2), и, используя правила, определенные в §14.8.2.1, мы найдем T
и N
путем сопоставления массива, переданного функции, с функциями параметр (который является ссылкой на массив). (Например, если arr
было int[10]
, T
было бы int
и N
было бы 10.) Как только мы получим их, можно будет создать экземпляр функции.
После создания тип возвращаемого значения функции будет char(&)[N]
*, ссылка на массив N
char
. (Если вам нужна помощь в разборе, см. §8.3.5. Есть также вопросы к SO о том, как анализировать «сложные» типы.) Итак, теперь мы нашли тип выражения, мы должны принять его размер.
§5.3.3 / 2 определяет, как sizeof
работает со ссылками и массивами (выделено мое):
При применении к ссылке или ссылочному типу результатом является размер ссылочного типа. При применении к классу результатом является число байтов в объекте этого класса, включая любые заполнение, необходимое для размещения объектов этого типа в массиве. Размер наиболее производного класса должен быть больше нуля (1,8). Результатом применения sizeof к подобъекту базового класса является размер базового класса type.70) При применении к массиву результатом является общее количество байтов в массиве. Это означает, что размер массива из n элементов в n раз больше размера элемента.
Размер ссылочного типа равен размеру его ссылочного типа, поэтому нам нужно задать размер char[N]
. Размер этого N * sizeof(char)
. char
является фундаментальным, так как это самый маленький тип, который есть; то есть sizeof(char)
- это всегда один. (§5.3.3 / 1) Таким образом, размер этого выражения равен 1 * N
, или то, что мы хотели все время: N
.
И вот как это работает.
Причина, по которой этот вариант предпочтительнее вашего последнего примера arrayCountof
, заключается в том, что результатом sizeof
является константное выражение , поэтому его можно использовать в местах, где требуется постоянное выражение. 1072 *
Следует отметить, что в C ++ 0x мы можем получить наш чистый синтаксис без макросов с помощью:
template <typename T, unsigned N>
constexpr size_t arrayCountof(T(&)[N]) {return N;}
* Причина, по которой тип возвращаемого значения функции является ссылкой на массив, а не массив, заключается в том, что вы не можете возвращать массивы. Если бы вы могли, хватит любого выбора.