Может ли этот макрос быть преобразован в функцию? - PullRequest
8 голосов
/ 18 сентября 2008

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

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

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

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

Ответы [ 16 ]

19 голосов
/ 18 сентября 2008

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

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

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

РЕДАКТИРОВАТЬ : Упрощено согласно JohnMcG . РЕДАКТИРОВАТЬ : встроенный.

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

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

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

Опубликованное решение Мартина Йорка также работает во время компиляции, но включает использование нестандартного оператора typeof () . Чтобы обойти это, либо дождитесь C ++ 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 - имя массива.

5 голосов
/ 19 сентября 2008

Пока никто не предлагал переносимый способ получения размера массива, когда у вас есть только экземпляр массива, а не его тип. (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-C ++.
  • Сбой с описательным сообщением об ошибке.
  • Не оценивает значение. (Я не могу придумать ситуацию, когда это будет проблемой, потому что тип массива не может быть возвращен функцией, но лучше быть в безопасности, чем потом сожалеть.)
  • Возвращает размер как константу времени компиляции.

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

5 голосов
/ 19 сентября 2008
Решение

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;
}

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

2 голосов
/ 18 сентября 2008

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

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

Обычно этот макрос называется что-то вроде NUM_ELEMENTS () или что-то, что указывает на его истинную полезность. Заменить макрос на функцию в C невозможно, но в 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)

Кроме того, книга Мэтью Уилсона "Несовершенный C ++" хорошо описывает происходящее здесь (Раздел 14.3 - стр. 211-213 - Массивы и указатели - dimensionof ()).

1 голос
/ 19 сентября 2008

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

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

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

К сожалению, версия, поставляемая '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];
}
1 голос
/ 18 сентября 2008

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

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

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

1 голос
/ 18 сентября 2008

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

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


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

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

1 голос
/ 18 сентября 2008

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

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

для (без знака i = 0; i

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

char * foo = new char [128];

для (без знака i = 0; i

Теперь вы будете выполнять итерацию для i = 0 до <1 и рвать на себе волосы. </p>

1 голос
/ 18 сентября 2008
  • функция, нет шаблонной функции, да
  • шаблон, я так думаю (но C ++
  • шаблоны это не мое)

Редактировать: Из кода Дуга

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) };
}

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

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

0 голосов
/ 06 октября 2008

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

...