Может ли этот макрос быть преобразован в функцию?

В то время как рефакторинг кода и избавление от всех тех #defines, которым мы теперь научены ненавидеть, я наткнулся на эту красоту, используемую для вычисления количества элементов в структуре:

#define STRUCTSIZE(s) (sizeof(s) / sizeof(*s))

Очень полезно, как есть, но может ли он быть преобразован в встроенную функцию или шаблон?

ОК, ARRAYSIZE будет лучшим именем, но это устаревший код (не знаю, откуда он пришел, ему не менее 15 лет), поэтому я вставил его как есть.

Ответ 1

Как уже было сказано, в коде фактически определяется количество элементов в массиве, а не struct. Я просто напишу divof() в явном виде, когда захочу. Если бы я должен был сделать это функцией, я хотел бы дать понять в своем определении, что он ожидает массив.

template<typename T,int SIZE>
inline size_t array_size(const T (&array)[SIZE])
{
    return SIZE;
}

Вышеупомянутое похоже на xtofl's, за исключением того, что оно защищает от передачи указателя на него (что указывает на динамически выделенный массив) и ошибочного получения ошибочного ответа.

ИЗМЕНИТЬ: Упрощен согласно JohnMcG. EDIT: встроенный.

К сожалению, приведенное выше не дает ответа на компиляцию (даже если компилятор делает встроенный и оптимизирует его как константу под капотом), поэтому не может использоваться как выражение постоянной времени компиляции. т.е. он не может использоваться как размер для объявления статического массива. В С++ 0x эта проблема исчезает, если заменить ключевое слово inline на constexpr (constexpr inline неявно).

constexpr size_t array_size(const T (&array)[SIZE])

Работа jwfearn для компиляции, но включает в себя наличие typedef, который эффективно "сохранил" размер массива в объявлении нового имени. Размер массива затем обрабатывается путем инициализации константы с помощью этого нового имени. В этом случае можно просто сохранить размер массива в константу с самого начала.

Опубликованное решение Martin York также работает во время компиляции, но включает использование нестандартного оператора typeof(). Работа над этим - либо ждать С++ 0x, либо использовать decltype (к тому времени на эту проблему не понадобится, поскольку у нас будет constexpr). Другой вариант - использовать Boost.Typeof, и в этом случае мы получим

#include <boost/typeof/typeof.hpp>

template<typename T>
struct ArraySize
{
    private:    static T x;
    public:     enum { size = sizeof(T)/sizeof(*x)};
};
template<typename T>
struct ArraySize<T*> {};

и используется при записи

ArraySize<BOOST_TYPEOF(foo)>::size

где foo - это имя массива.

Ответ 2

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

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

Здесь мое решение:

// asize.hpp
template < typename T >
struct asize; // no implementation for all types...

template < typename T, size_t N >
struct asize< T[N] > { // ...except arrays
    static const size_t val = N;
};

template< size_t N  >
struct count_type { char val[N]; };

template< typename T, size_t N >
count_type< N > count( const T (&)[N] ) {}

#define ASIZE( a ) ( sizeof( count( a ).val ) ) 
#define ASIZET( A ) ( asize< A >::val ) 

с тестовым кодом (используя Boost.StaticAssert, чтобы продемонстрировать использование только времени компиляции):

// asize_test.cpp
#include <boost/static_assert.hpp>
#include "asize.hpp"

#define OLD_ASIZE( a ) ( sizeof( a ) / sizeof( *a ) )

typedef char C;
typedef struct { int i; double d; } S;
typedef C A[42];
typedef S B[42];
typedef C * PA;
typedef S * PB;

int main() {
    A a; B b; PA pa; PB pb;
    BOOST_STATIC_ASSERT( ASIZET( A ) == 42 );
    BOOST_STATIC_ASSERT( ASIZET( B ) == 42 );
    BOOST_STATIC_ASSERT( ASIZET( A ) == OLD_ASIZE( a ) );
    BOOST_STATIC_ASSERT( ASIZET( B ) == OLD_ASIZE( b ) );
    BOOST_STATIC_ASSERT( ASIZE( a ) == OLD_ASIZE( a ) );
    BOOST_STATIC_ASSERT( ASIZE( b ) == OLD_ASIZE( b ) );
    BOOST_STATIC_ASSERT( OLD_ASIZE( pa ) != 42 ); // logic error: pointer accepted
    BOOST_STATIC_ASSERT( OLD_ASIZE( pb ) != 42 ); // logic error: pointer accepted
 // BOOST_STATIC_ASSERT( ASIZE( pa ) != 42 ); // compile error: pointer rejected
 // BOOST_STATIC_ASSERT( ASIZE( pb ) != 42 ); // compile error: pointer rejected
    return 0;
}

Это решение отклоняет типы не-массива во время компиляции, поэтому он не будет путать указатели по версии макроса.

Ответ 3

Никто до сих пор не предлагал переносимый способ получить размер массива, когда у вас есть только экземпляр массива, а не его тип. (typeof и _countof не переносимы, поэтому их нельзя использовать.)

Я бы сделал это следующим образом:

template<int n>
struct char_array_wrapper{
    char result[n];
};

template<typename T, int s>
char_array_wrapper<s> the_type_of_the_variable_is_not_an_array(const T (&array)[s]){
}


#define ARRAYSIZE_OF_VAR(v) sizeof(the_type_of_the_variable_is_not_an_array(v).result)

#include <iostream>
using namespace std;

int main(){
    int foo[42];
    int*bar;
    cout<<ARRAYSIZE_OF_VAR(foo)<<endl;
    // cout<<ARRAYSIZE_OF_VAR(bar)<<endl;  fails
}
  • Он работает, когда только значение находится вокруг.
  • Он переносимый и использует только std-С++.
  • Ошибка с сообщением об ошибке описания.
  • Он не оценивает значение. (Я не могу придумать ситуацию, когда это будет проблемой, потому что тип массива не может быть возвращен функцией, но лучше быть в безопасности, чем сожалеть.)
  • Он возвращает размер как константу compiletime.

Я завернул конструкцию в макрос, чтобы иметь достойный синтаксис. Если вы хотите избавиться от него, ваш единственный вариант - сделать подстановку вручную.

Ответ 4

Макрос имеет очень вводящее в заблуждение имя - выражение в макросе возвращает количество элементов в массиве, если имя массива передается в качестве параметра макроса.

Для других типов вы получите что-то более или менее бессмысленное, если тип является указателем или вы получите синтаксическую ошибку.

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

Используемая версия основана на коде в заголовке Microsoft winnt.h(пожалуйста, дайте мне знать, если публикация этого фрагмента выходит за рамки справедливого использования):

//
// Return the number of elements in a statically sized array.
//   DWORD Buffer[100];
//   RTL_NUMBER_OF(Buffer) == 100
// This is also popularly known as: NUMBER_OF, ARRSIZE, _countof, NELEM, etc.
//
#define RTL_NUMBER_OF_V1(A) (sizeof(A)/sizeof((A)[0]))

#if defined(__cplusplus) && \
    !defined(MIDL_PASS) && \
    !defined(RC_INVOKED) && \
    !defined(_PREFAST_) && \
    (_MSC_FULL_VER >= 13009466) && \
    !defined(SORTPP_PASS)
//
// 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)))

//
// This does not work with:
//
// void Foo()
// {
//    struct { int x; } y[2];
//    RTL_NUMBER_OF_V2(y); // illegal use of anonymous local type in template instantiation
// }
//
// You must instead do:
//
// struct Foo1 { int x; };
//
// void Foo()
// {
//    Foo1 y[2];
//    RTL_NUMBER_OF_V2(y); // ok
// }
//
// OR
//
// void Foo()
// {
//    struct { int x; } y[2];
//    RTL_NUMBER_OF_V1(y); // ok
// }
//
// OR
//
// void Foo()
// {
//    struct { int x; } y[2];
//    _ARRAYSIZE(y); // ok
// }
//

#else
#define RTL_NUMBER_OF_V2(A) RTL_NUMBER_OF_V1(A)
#endif

#ifdef ENABLE_RTL_NUMBER_OF_V2
#define RTL_NUMBER_OF(A) RTL_NUMBER_OF_V2(A)
#else
#define RTL_NUMBER_OF(A) RTL_NUMBER_OF_V1(A)
#endif

//
// ARRAYSIZE is more readable version of RTL_NUMBER_OF_V2, and uses
// it regardless of ENABLE_RTL_NUMBER_OF_V2
//
// _ARRAYSIZE is a version useful for anonymous types
//
#define ARRAYSIZE(A)    RTL_NUMBER_OF_V2(A)
#define _ARRAYSIZE(A)   RTL_NUMBER_OF_V1(A)

Кроме того, книга Мэтью Уилсона "Imperfect С++" имеет хорошее отношение к тому, что происходит здесь (раздел 14.3 - стр. 211-213). Массивы и указатели - размерность()).

Ответ 5

  • нет функции шаблона, да
  • я так думаю (но С++
  • шаблоны - это не мое дело)

Изменить: Код Doug

template <typename T>
uint32_t StructSize()  // This might get inlined to a constant at compile time
{
   return sizeof(T)/sizeof(*T);
}

// or to get it at compile time for shure

class StructSize<typename T>
{
   enum { result = sizeof(T)/sizeof(*T) };
}

Мне сказали, что второй не работает. OTOH что-то вроде этого должно быть работоспособным, я просто не использую С++, чтобы его исправить.

Страница на шаблонах С++ (и D) для компиляции времени

Ответ 6

Ваш макрос не указан, его следует называть ARRAYSIZE. Он используется для определения количества элементов в массиве, размер которого фиксирован во время компиляции. Здесь он может работать:

char foo [128];//На самом деле, вы бы имеют постоянную или постоянную выражение как размер массива.

для (без знака я = 0; я < STRUCTSIZE ( foo); ++ i) {}

Это немного хрупкое, потому что вы можете сделать эту ошибку:

char * foo = new char [128];

для (без знака я = 0; я < STRUCTSIZE ( foo); ++ i) {}

Теперь вы будете перебирать для я = 0 в < 1 и вырвать волосы.

Ответ 7

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

template< typename T > size_t structsize( const T& t ) { 
  return sizeof( t ) / sizeof( *t ); 
}


int ints[] = { 1,2,3 };
assert( structsize( ints ) == 3 );

Но я согласен, что это не работает для structs: он работает для массивов. Поэтому я бы скорее назвал Arraysize:)

Ответ 8

Simplfying @KTC, так как мы имеем размер массива в аргументе шаблона:

template<typename T, int SIZE>
int arraySize(const T(&arr)[SIZE])
{
    return SIZE;
}

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

Ответ 9

Я предпочитаю метод перечисления, предложенный [BCS] (в Может ли этот макрос быть преобразован в функцию?)

Это потому, что вы можете использовать его, где компилятор ожидает постоянной времени компиляции. Текущая версия языка не позволяет использовать результаты функций для compile time const, но я считаю, что это происходит в следующей версии компилятора:

Проблема с этим методом заключается в том, что он не генерирует ошибку времени компиляции при использовании с классом, который перегрузил оператор "*" (подробнее см. код ниже).

К сожалению, версия, предоставленная "BCS", не совсем компилируется так, как ожидалось, вот моя версия:

#include <iterator>
#include <algorithm>
#include <iostream>


template<typename T>
struct StructSize
{
    private:    static T x;
    public:      enum { size = sizeof(T)/sizeof(*x)};
};

template<typename T>
struct StructSize<T*>
{
    /* Can only guarantee 1 item (maybe we should even disallow this situation) */
    //public:     enum { size = 1};
};

struct X
{
    int operator *();
};


int main(int argc,char* argv[])
{
    int data[]                                  = {1,2,3,4,5,6,7,8};
    int copy[ StructSize<typeof(data)>::size];

    std::copy(&data[0],&data[StructSize<typeof(data)>::size],&copy[0]);
    std::copy(&copy[0],&copy[StructSize<typeof(copy)>::size],std::ostream_iterator<int>(std::cout,","));

    /*
     * For extra points we should make the following cause the compiler to generate an error message */
    X   bad1;
    X   bad2[StructSize<typeof(bad1)>::size];
}

Ответ 10

Да, это может быть сделано в С++

template <typename T>
size_t getTypeSize()
{
   return sizeof(T)/sizeof(*T);
}

для использования:

struct JibbaJabba
{
   int int1;
   float f;
};

int main()
{
    cout << "sizeof JibbaJabba is " << getTypeSize<JibbaJabba>() << std::endl;
    return 0;
}

См. сообщение BCS выше или ниже о крутом способе сделать это с классом во время компиляции с использованием метапрограммирования легкого шаблона.

Ответ 11

Я не думаю, что это действительно работает с количеством элементов в структуре. Если структура упакована, и вы использовали меньше, чем размер указателя (например, char в 32-битной системе), то ваши результаты ошибочны. Кроме того, если структура содержит структуру, вы тоже ошибаетесь!

Ответ 12

xtofl имеет правильный ответ для нахождения размера массива. Никаких макросов или шаблонов не требуется для нахождения размера структуры, так как sizeof() должен делать красиво.

Я согласен, что препроцессор злой, но бывают случаи, когда это наименьшее зло из альтернатив.

Ответ 13

Как ответ JohnMcG, но

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

Вот почему вы сделали бы его встроенным функцией шаблона.

Ответ 16

Для массивов переменной длины в стиле C99, похоже, что подход с чистым макросом (sizeof (arr)/sizeof (arr [0])) является единственным, который будет работать.