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

StackOverflow https://stackoverflow.com/questions/95500

  •  01-07-2019
  •  | 
  •  

Вопрос

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

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

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

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

Это было полезно?

Решение

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

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

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

Редактировать:Упрощенный в соответствии с JohnMcG. Редактировать:встроенный.

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

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

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

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

где фу это имя массива.

Другие советы

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

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

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 Повысить.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;
}

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

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

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

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

Версия, которую я использую, основана на коде в заголовке winnt от Microsoft.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()).

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

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

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

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

char* foo = новый символ[128];

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

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

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

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


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

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

Упрощаем @KTC, поскольку у нас есть размер массива в аргументе шаблона:

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

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

  • функция, нет шаблонной функции, да
  • шаблон, я так думаю (но 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) для работы во время компиляции

Я предпочитаю метод перечисления, предложенный [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];
}

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

Да, это можно сделать шаблоном на C ++

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 выше или ниже о классном способе сделать это с классом во время компиляции, используя какой-нибудь легкий шаблон метапрограммирования.

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

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

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

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

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

Специфичные для Windows:

Есть макрос _countof() поставляется ЭЛТ именно для этой цели.

Ссылка на документ в MSDN

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top