Время компиляции sizeof_array без использования макроса - PullRequest
15 голосов
/ 01 октября 2009

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

Вот так:

Чтобы получить количество элементов в стандартном массиве C ++, я мог бы использовать либо макрос (1), либо безопасную для типов встроенную функцию (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]));
}

Ответы [ 10 ]

21 голосов
/ 01 октября 2009

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

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»

16 голосов
/ 01 октября 2009

В C ++ 1x constexpr вы получите:

template <typename T, size_t N>
constexpr size_t countof(T(&)[N])
{
    return N;
}
7 голосов
/ 01 октября 2009

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

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]

Если подумать, я считаю, что это невозможно сделать за один «функциональный вызов» в C ++ 03, кроме макросов, и вот почему.

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

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

6 голосов
/ 06 июня 2011

Проблема

Мне нравится Ответ Адисака :

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

Это то, что Microsoft использует для макроса _countof в VS2008, и у него есть несколько приятных функций:

  • Работает в время компиляции
  • Это типобезопасно (то есть, если вы дадите ему указатель, он сгенерирует ошибку во время компиляции, которая слишком легко ухудшает массив)

Но, как указал Георг , этот подход использует шаблоны, поэтому не гарантирует работу с локальными типами для C ++ 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*);
};

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

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

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

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

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

5 голосов
/ 01 октября 2009

Это не совсем то, что вы ищете, но это близко - фрагмент из 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().

В книге Мэтью Уилсона "Несовершенный C ++" также обсуждаются методы, которые здесь используются.

4 голосов
/ 01 октября 2009

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

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

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

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

3 голосов
/ 01 октября 2009

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

2 голосов
/ 01 октября 2009

Без C ++ 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;
}

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

2 голосов
/ 01 октября 2009

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

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

Лучше:

template <typename T, size_t N>
size_t sizeof_array(T (&)[N]){
    return N;
}
0 голосов
/ 01 августа 2017

Теперь библиотеки 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

...