Скомпилировать время sizeof_array без использования макроса

Это что-то, что беспокоило меня последние пару дней, я не думаю, что это можно решить, но раньше я видел шаблонную магию.

Здесь:

Чтобы получить количество элементов в стандартном массиве С++, я мог бы использовать либо макрос (1), либо встроенную функцию typeafe (2):

(1)

#define sizeof_array(ARRAY) (sizeof(ARRAY)/sizeof(ARRAY[0]))

(2)

template <typename T>
size_t sizeof_array(const T& ARRAY){
    return (sizeof(ARRAY)/sizeof(ARRAY[0]));
}

Как вы можете видеть, первая проблема заключается в том, что она является макросом (на данный момент я считаю, что проблема), а другая имеет проблему неспособности получить размер массива во время компиляции; т.е. я не могу написать:

enum ENUM{N=sizeof_array(ARRAY)};

или

BOOST_STATIC_ASSERT(sizeof_array(ARRAY)==10);// Assuming the size 10..

Кто-нибудь знает, можно ли это решить?

Обновление

Этот вопрос был создан до введения constexpr. В настоящее время вы можете просто использовать:

template <typename T>
constexpr auto sizeof_array(const T& iarray) {
    return (sizeof(iarray) / sizeof(iarray[0]));
}

Ответ 1

Попробуйте следующее из здесь:

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

int testarray[10];
enum { testsize = mycountof(testarray) };

void test() {
    printf("The array count is: %d\n", testsize);
}

Он должен распечатать: "Количество массивов: 10"

Ответ 2

В С++ 1x constexpr вы получите следующее:

template <typename T, size_t N>
constexpr size_t countof(T(&)[N])
{
    return N;
}

Ответ 3

Лучшее, о чем я могу думать, это:

template <class T, std::size_t N>
char (&sizeof_array(T (&a)[N]))[N];

// As litb noted in comments, you need this overload to handle array rvalues
// correctly (e.g. when array is a member of a struct returned from function),
// since they won't bind to non-const reference in the overload above.
template <class T, std::size_t N>
char (&sizeof_array(const T (&a)[N]))[N];

который должен использоваться с другим sizeof:

int main()
{
    int a[10];
    int n = sizeof(sizeof_array(a));
    std::cout << n << std::endl;
}

[EDIT]

Подумайте об этом, я считаю, что это невозможно сделать в одном "функциональном вызове" в С++ 03, помимо макросов, и вот почему.

С одной стороны, вам явно потребуется вычесть параметр шаблона, чтобы получить размер массива (либо напрямую, либо через sizeof, как и вы). Однако вывод параметра шаблона применим только к функциям, а не к классам; т.е. у вас может быть параметр шаблона R типа reference-to-array-of-N, где N - это еще один параметр шаблона, но вам нужно предоставить как R, так и N в точке вызова; если вы хотите вывести N из R, это может сделать только вызов функции.

С другой стороны, единственный способ, которым любое выражение, включающее вызов функции, может быть постоянным, - это когда оно находится внутри sizeof. Все остальное (например, доступ к статическому или перечисляемому элементу по возвращаемому значению функции) по-прежнему требует вызова функции, что, очевидно, означает, что это не будет постоянным выражением.

Ответ 4

Проблема

Мне нравится Adisak answer:

template <typename T, size_t N>
char ( &_ArraySizeHelper( T (&arr)[N] ))[N];
#define COUNTOF( arr ) (sizeof( _ArraySizeHelper( arr ) ))

Это то, что Microsoft использует для _ countof macro в VS2008, и получилось несколько приятных функций:

  • Он работает в время компиляции
  • Он typeafe (т.е. будет генерировать ошибку времени компиляции, если вы укажете ему указатель, с которым слишком легко сбрасываются массивы)

Но, как указано Georg, этот подход использует шаблоны, поэтому не гарантирует работу с локальными типами для С++ 03:

void i_am_a_banana() {
  struct { int i; } arr[10];
  std::cout << COUNTOF(arr) << std::endl; // forbidden in C++03
}

К счастью, нам не повезло.

Решение

Иван Джонсон придумал умный подход, который выигрывает во всех аккаунтах: он набирает время, время компиляции и работает с локальными типами:

#define COUNTOF(arr) ( \
   0 * sizeof(reinterpret_cast<const ::Bad_arg_to_COUNTOF*>(arr)) + \
   0 * sizeof(::Bad_arg_to_COUNTOF::check_type((arr), &(arr))) + \
   sizeof(arr) / sizeof((arr)[0]) )

struct Bad_arg_to_COUNTOF {
   class Is_pointer; // incomplete
   class Is_array {};
   template <typename T>
   static Is_pointer check_type(const T*, const T* const*);
   static Is_array check_type(const void*, const void*);
};

Для тех, кто заинтересован, он работает, вставляя два "теста" перед стандартным макросом размера массива размером на основе. Эти тесты не влияют на окончательный расчет, но предназначены для генерации ошибок компиляции для типов без массива:

  • Первый тест завершается с ошибкой, если arr не является интегралом, перечислением, указателем или массивом. reinterpret_cast<const T*> не работает для других типов.
  • Второй тест не выполняется для целых, перечисляемых или указательных типов.

    Интегральные и перечисляемые типы будут терпеть неудачу, потому что нет версии check_type, которую они соответствуют, поскольку check_type ожидает указателей.

    Типы указателей будут терпеть неудачу, потому что они будут соответствовать шаблонизированной версии check_type, но возвращаемый тип (Is_pointer) для шаблона check_type является неполным, что приведет к ошибке.

    Типы массивов пройдут, поскольку принимают адрес массива типа T даст вам T (*)[], так же как указатель на массив, а не указатель на указатель. Это означает, что шаблонная версия check_type не будет соответствовать. Благодаря SFINAE компилятор перейдет к не templated версии check_type, которая должна принимать любую пару указателей. Так как тип возврата для не templated версии полностью определен, ошибки не будет. И поскольку теперь мы не имеем дело с шаблонами, локальные типы работают нормально.

Ответ 5

Это не совсем то, что вы ищете, но оно близко - фрагмент от winnt.h, который включает некоторое объяснение того, что делает # $% ^:

//
// RtlpNumberOf is a function that takes a reference to an array of N Ts.
//
// typedef T array_of_T[N];
// typedef array_of_T &reference_to_array_of_T;
//
// RtlpNumberOf returns a pointer to an array of N chars.
// We could return a reference instead of a pointer but older compilers do not accept that.
//
// typedef char array_of_char[N];
// typedef array_of_char *pointer_to_array_of_char;
//
// sizeof(array_of_char) == N
// sizeof(*pointer_to_array_of_char) == N
//
// pointer_to_array_of_char RtlpNumberOf(reference_to_array_of_T);
//
// We never even call RtlpNumberOf, we just take the size of dereferencing its return type.
// We do not even implement RtlpNumberOf, we just decare it.
//
// Attempts to pass pointers instead of arrays to this macro result in compile time errors.
// That is the point.
//
extern "C++" // templates cannot be declared to have 'C' linkage
template <typename T, size_t N>
char (*RtlpNumberOf( UNALIGNED T (&)[N] ))[N];

#define RTL_NUMBER_OF_V2(A) (sizeof(*RtlpNumberOf(A)))

Макрос RTL_NUMBER_OF_V2() в конечном итоге используется в более читаемом макросе ARRAYSIZE().

Книга Мэтью Уилсона "Imperfect С++" также содержит обсуждение тех методов, которые используются здесь.

Ответ 6

Если вы находитесь на платформе Microsoft, вы можете воспользоваться макросом _ countof. Это нестандартное расширение, которое возвращает количество элементов в массиве. Преимущество большинства макросов стиля countof заключается в том, что оно вызовет ошибку компиляции, если она используется для типа без массива.

Следующее работает просто отлично (VS 2008 RTM)

static int ARRAY[5];
enum ENUM{N=_countof(ARRAY)};

Но еще раз, это MS, поэтому это может не сработать для вас.

Ответ 7

Вы не можете его решить в общем случае, это одна из причин для массивных оберток, таких как boost array (плюс стиль stl-стиля, конечно).

Ответ 8

Кажется, что невозможно получить массив sizeof как константу времени компиляции без макроса с текущим стандартом С++ (вам нужна функция для определения размера массива, но вызовы функций не разрешены там, где вам требуется компиляция, постоянная времени). [Изменить: Но см. Минаев блестящее решение!]

Тем не менее, ваша версия шаблона также не является типичной, и она страдает от той же проблемы, что и макрос: она также принимает указатели и, в частности, массивы, разлагающиеся на указатель. Когда он принимает указатель, результат sizeof (T *)/sizeof (T) не может быть значимым.

лучше:

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

Ответ 9

Без С++ 0x ближайший я могу получить:

#include <iostream>

template <typename T>
struct count_of_type
{
};


template <typename T, unsigned N>
struct count_of_type<T[N]> 
{
    enum { value = N };
};

template <typename T, unsigned N>
unsigned count_of ( const T (&) [N] )
{
    return N;
};


int main ()
{
    std::cout << count_of_type<int[20]>::value << std::endl;
    std::cout << count_of_type<char[42]>::value << std::endl;

    // std::cout << count_of_type<char*>::value << std::endl; // compile error

    int foo[1234];

    std::cout << count_of(foo) << std::endl;

    const char* bar = "wibble";

    // std::cout << count_of( bar ) << std::endl; // compile error

    enum E1 { N = count_of_type<int[1234]>::value } ;

    return 0;
}

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

Ответ 10

Теперь доступны библиотеки STL для выбора/выбора времени компиляции массива

#include <iostream>
#include <array>

template<class T>
void test(T t)
{
    int a[std::tuple_size<T>::value]; // can be used at compile time
    std::cout << std::tuple_size<T>::value << '\n';
}

int main()
{
    std::array<float, 3> arr;
    test(arr);
}

Вывод:   3