Pregunta

Mientras refactorizaba el código y me deshacía de todos esos #defines que ahora nos han enseñado a odiar, me encontré con esta belleza que se usa para calcular la cantidad de elementos en una estructura:

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

Muy útil como es, pero ¿se puede convertir en una función o plantilla en línea?

OK, ARRAYSIZE sería un mejor nombre, pero este es un código heredado (no tengo idea de dónde vino, tiene al menos 15 años), así que lo pegué "tal cual".

¿Fue útil?

Solución

Como se indicó, el código en realidad calcula la cantidad de elementos en una matriz, no la estructura.Simplemente escribiría la división sizeof() explícitamente cuando la quiera.Si tuviera que convertirlo en una función, me gustaría dejar claro en su definición que espera una matriz.

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

Lo anterior es similar a xtofl, excepto que evita pasarle un puntero (que dice apuntar a una matriz asignada dinámicamente) y obtener una respuesta incorrecta por error.

EDITAR:Simplificado según JuanMcG. EDITAR:en línea.

Desafortunadamente, lo anterior no proporciona una respuesta en tiempo de compilación (incluso si el compilador lo integra y optimiza para que sea una constante interna), por lo que no se puede utilizar como una expresión constante en tiempo de compilación.es decir.No se puede utilizar como tamaño para declarar una matriz estática.En C++ 0x, este problema desaparece si se reemplaza la palabra clave en línea por constexpr (constexpr está en línea implícitamente).

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

jwfearn La solución funciona en tiempo de compilación, pero implica tener un typedef que efectivamente "guardó" el tamaño de la matriz en la declaración de un nuevo nombre.Luego, el tamaño de la matriz se calcula inicializando una constante mediante ese nuevo nombre.En tal caso, también se puede simplemente guardar el tamaño de la matriz en una constante desde el principio.

Martín York La solución publicada también funciona en tiempo de compilación, pero implica el uso de la versión no estándar. tipo de() operador.La solución a esto es esperar a C++ 0x y usar tipo decl (para entonces ya no lo necesitaríamos para este problema ya que tendremos constexpr).Otra alternativa es usar Boost.Typeof, en cuyo caso terminaremos con

#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*> {};

y se utiliza escribiendo

ArraySize<BOOST_TYPEOF(foo)>::size

dónde foo es el nombre de una matriz.

Otros consejos

Hasta ahora, ninguno ha propuesto una forma portátil de obtener el tamaño de una matriz cuando solo se tiene una instancia de una matriz y no su tipo.(typeof y _countof no son portátiles, por lo que no se pueden usar).

Yo lo haría de la siguiente manera:

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
}
  • Funciona cuando solo el valor está disponible.
  • Es portátil y sólo utiliza std-C++.
  • Falla con un mensaje de error descriptivo.
  • No evalúa el valor.(No se me ocurre una situación en la que esto sea un problema porque una función no puede devolver el tipo de matriz, pero más vale prevenir que lamentar).
  • Devuelve el tamaño como constante de tiempo de compilación.

Envolví la construcción en una macro para tener una sintaxis decente.Si desea deshacerse de él, su única opción es realizar la sustitución manualmente.

KTCLa solución de es limpia pero no se puede usar en tiempo de compilación y depende de la optimización del compilador para evitar la sobrecarga de código y la sobrecarga de llamadas a funciones.

Se puede calcular el tamaño de la matriz con una metafunción solo en tiempo de compilación con costo de tiempo de ejecución cero. BCS Estaba en el camino correcto pero esa solución es incorrecta.

Aquí está mi solución:

// 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 ) 

con código de prueba (usando Impulsar.StaticAssert para demostrar el uso solo en tiempo de compilación):

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

Esta solución rechaza los tipos que no son de matriz en el momento de la compilación, por lo que no se confundirá con los punteros como lo hace la versión macro.

La macro tiene un nombre muy engañoso: la expresión de la macro devolverá el número de elementos de una matriz si el nombre de una matriz se pasa como parámetro de macro.

Para otros tipos obtendrás algo más o menos sin sentido si el tipo es un puntero o obtendrás un error de sintaxis.

Por lo general, esa macro se llama algo así como NUM_ELEMENTS() o algo así para indicar su verdadera utilidad.No es posible reemplazar la macro con una función en C, pero en C++ se puede usar una plantilla.

La versión que uso se basa en el código del encabezado winnt.h de Microsoft (avíseme si publicar este fragmento va más allá del uso legítimo):

//
// 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)

Además, el libro de Matthew Wilson "Imperfect C++" tiene un buen tratamiento de lo que está sucediendo aquí (Sección 14.3 - páginas 211-213 - Matrices y punteros - dimensión de()).

Su macro tiene un nombre incorrecto, debería llamarse ARRAYSIZE.Se utiliza para determinar la cantidad de elementos en una matriz cuyo tamaño se fija en el momento de la compilación.He aquí una forma en que puede funcionar:

charfoo[128];// En realidad, tendrías una expresión constante o constante como tamaño de matriz.

para (sin signo i = 0;i <structsize (foo);++yo) { }

Es un poco frágil de usar porque puedes cometer este error:

char* foo = nuevo char[128];

para (sin signo i = 0;i <structsize (foo);++yo) { }

Ahora iterarás para i = 0 a <1 y te arrancarás el pelo.

El tipo de una función de plantilla se deduce automáticamente, a diferencia del de una clase de plantilla.Puedes usarlo aún más simple:

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


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

Pero estoy de acuerdo en que no funciona para estructuras:Funciona para matrices.Entonces prefiero llamarlo Arraysize :)

Simplificando @KTC, ya que tenemos el tamaño de la matriz en el argumento de la plantilla:

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

La desventaja es que tendrá una copia de esto en su binario para cada combinación de Nombre de tipo y Tamaño.

  • función, sin función de plantilla, sí
  • plantilla, creo que sí (pero C++
  • las plantillas no son lo mio)

Editar: Del código de 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) };
}

Me han dicho que el segundo no funciona.OTOH, algo así debería ser viable, simplemente no uso C++ lo suficiente para solucionarlo.

Una página sobre plantillas de C++ (y D) para temas de tiempo de compilación

Prefiero el método enum sugerido por [BCS](en ¿Se puede convertir esta macro en una función?)

Esto se debe a que puede usarlo cuando el compilador espera una constante de tiempo de compilación.La versión actual del lenguaje no le permite usar resultados de funciones para constantes de tiempo de compilación, pero creo que esto vendrá en la próxima versión del compilador:

El problema con este método es que no genera un error en tiempo de compilación cuando se usa con una clase que ha sobrecargado el operador '*' (consulte el código a continuación para obtener más detalles).

Lamentablemente, la versión proporcionada por 'BCS' no se compila como se esperaba, así que aquí está mi versión:

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

No creo que eso realmente resuelva la cantidad de elementos en una estructura.Si la estructura está empaquetada y usó elementos más pequeños que el tamaño del puntero (como char en un sistema de 32 bits), sus resultados son incorrectos.Además, si la estructura contiene una estructura, ¡también estás equivocado!

Sí, se puede hacer una plantilla en C++.

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

usar:

struct JibbaJabba
{
   int int1;
   float f;
};

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

Consulte la publicación de BCS arriba o abajo sobre una manera interesante de hacer esto con una clase en tiempo de compilación usando alguna metaprogramación de plantilla ligera.

xtofl tiene la respuesta correcta para encontrar el tamaño de una matriz.No debería ser necesaria ninguna macro o plantilla para encontrar el tamaño de una estructura, ya que sizeof() debería funcionar bien.

Estoy de acuerdo con el preprocesador es malo, pero hay ocasiones en las que es el el menor mal de las alternativas.

Como respuesta de JohnMcG, pero

La desventaja es que tendrá una copia de esto en su binario para cada combinación de Nombre de tipo y Tamaño.

Por eso lo convertirías en un en línea función de plantilla.

Específico de Windows:

ahí está la macro _countof() suministrado por el CRT exactamente para este propósito.

Un enlace al documento en MSDN

Para matrices de longitud variable estilo C99, parece que el enfoque macro puro (sizeof(arr) / sizeof(arr[0])) es el único que funcionará.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top